Skip to content

Commit

Permalink
Initial implementation for JsPromise and TaskBuilder
Browse files Browse the repository at this point in the history
  • Loading branch information
kjvalencik committed Sep 14, 2021
1 parent cb884ee commit 0803489
Show file tree
Hide file tree
Showing 24 changed files with 704 additions and 54 deletions.
4 changes: 2 additions & 2 deletions .cargo/config.toml
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
[alias]
# Neon defines mutually exclusive feature flags which prevents using `cargo clippy --all-features`
# The following aliases simplify linting the entire workspace
check-napi = "check --all-targets --no-default-features -p neon -p neon-runtime -p neon-build -p neon-macros -p electron-tests -p napi-tests --features proc-macros,try-catch-api,napi-experimental"
check-napi = "check --all-targets --no-default-features -p neon -p neon-runtime -p neon-build -p neon-macros -p electron-tests -p napi-tests --features proc-macros,try-catch-api,napi-experimental,promise-api,task-api"
check-legacy = "check --all-targets --no-default-features -p neon -p neon-runtime -p neon-build -p neon-macros -p tests -p static_tests --features event-handler-api,proc-macros,try-catch-api,legacy-runtime"
clippy-legacy = "clippy --all-targets --no-default-features -p neon -p neon-runtime -p neon-build -p neon-macros -p tests -p static_tests --features event-handler-api,proc-macros,try-catch-api,legacy-runtime -- -A clippy::missing_safety_doc"
clippy-napi = "clippy --all-targets --no-default-features -p neon -p neon-runtime -p neon-build -p neon-macros -p electron-tests -p napi-tests --features proc-macros,try-catch-api,napi-experimental -- -A clippy::missing_safety_doc"
neon-test = "test --no-default-features --features napi-experimental"
neon-doc = "rustdoc --no-default-features --features=channel-api,napi-experimental,proc-macros,try-catch-api -- --cfg docsrs"
neon-doc = "rustdoc --no-default-features --features=channel-api,napi-experimental,proc-macros,try-catch-api,promise-api,task-api -- --cfg docsrs"
7 changes: 7 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,13 @@ event-queue-api = ["channel-api"]
# Feature flag to include procedural macros
proc-macros = ["neon-macros"]

# Enable `JsPromise` and `Deferred`
# https://github.com/neon-bindings/rfcs/pull/35
promise-api = []
# Enable `TaskBuilder`
# https://github.com/neon-bindings/rfcs/pull/35
task-api = []

[package.metadata.docs.rs]
no-default-features = true
rustdoc-args = ["--cfg", "docsrs"]
Expand Down
103 changes: 103 additions & 0 deletions crates/neon-runtime/src/napi/async_work.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
use std::ffi::c_void;
use std::mem;
use std::ptr;

use crate::napi::bindings as napi;
use crate::raw::Env;

type Execute<T, O> = fn(input: T) -> O;
type Complete<O> = fn(env: Env, output: O);

/// Schedule work to execute on the libuv thread pool
///
/// # Safety
/// * `env` must be a valid `napi_env` for the current thread
pub unsafe fn schedule<T, O>(env: Env, input: T, execute: Execute<T, O>, complete: Complete<O>)
where
T: Send + 'static,
O: Send + 'static,
{
let mut data = Box::new(Data {
state: State::Input(input),
execute,
complete,
work: ptr::null_mut(),
});

let work = &mut data.work as *mut _;

assert_eq!(
napi::create_async_work(
env,
ptr::null_mut(),
super::string(env, "neon_async_work"),
Some(call_execute::<T, O>),
Some(call_complete::<T, O>),
Box::into_raw(data).cast(),
work,
),
napi::Status::Ok,
);

match napi::queue_async_work(env, *work) {
napi::Status::Ok => {}
status => {
napi::delete_async_work(env, *work);
assert_eq!(status, napi::Status::Ok);
}
}
}

struct Data<T, O> {
state: State<T, O>,
execute: Execute<T, O>,
complete: Complete<O>,
work: napi::AsyncWork,
}

enum State<T, O> {
Input(T),
Output(O),
Empty,
}

impl<T, O> State<T, O> {
fn take_input(&mut self) -> Option<T> {
match mem::replace(self, Self::Empty) {
Self::Input(input) => Some(input),
_ => None,
}
}

fn take_output(&mut self) -> Option<O> {
match mem::replace(self, Self::Empty) {
Self::Output(output) => Some(output),
_ => None,
}
}
}

unsafe extern "C" fn call_execute<T, O>(_: Env, data: *mut c_void) {
let data = &mut *data.cast::<Data<T, O>>();
let input = data.state.take_input().unwrap();
let output = (data.execute)(input);

data.state = State::Output(output);
}

unsafe extern "C" fn call_complete<T, O>(env: Env, status: napi::Status, data: *mut c_void) {
let Data {
mut state,
complete,
work,
..
} = *Box::<Data<T, O>>::from_raw(data.cast());

napi::delete_async_work(env, work);

match status {
napi::Status::Ok => complete(env, state.take_output().unwrap()),
napi::Status::Cancelled => {}
_ => assert_eq!(status, napi::Status::Ok),
}
}
17 changes: 17 additions & 0 deletions crates/neon-runtime/src/napi/bindings/functions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ mod napi1 {
fn is_buffer(env: Env, value: Value, result: *mut bool) -> Status;
fn is_error(env: Env, value: Value, result: *mut bool) -> Status;
fn is_array(env: Env, value: Value, result: *mut bool) -> Status;
fn is_promise(env: Env, value: Value, result: *mut bool) -> Status;

fn get_value_string_utf8(
env: Env,
Expand Down Expand Up @@ -209,6 +210,22 @@ mod napi1 {
) -> Status;

fn run_script(env: Env, script: Value, result: *mut Value) -> Status;

fn create_async_work(
env: Env,
async_resource: Value,
async_resource_name: Value,
execute: AsyncExecuteCallback,
complete: AsyncCompleteCallback,
data: *mut c_void,
result: *mut AsyncWork,
) -> Status;

fn delete_async_work(env: Env, work: AsyncWork) -> Status;
fn queue_async_work(env: Env, work: AsyncWork) -> Status;
fn create_promise(env: Env, deferred: *mut Deferred, promise: *mut Value) -> Status;
fn resolve_deferred(env: Env, deferred: Deferred, resolution: Value) -> Status;
fn reject_deferred(env: Env, deferred: Deferred, rejection: Value) -> Status;
}
);
}
Expand Down
2 changes: 1 addition & 1 deletion crates/neon-runtime/src/napi/bindings/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -169,8 +169,8 @@ macro_rules! generate {
use std::sync::Once;

pub(crate) use functions::*;
pub use types::TypedArrayType;
pub(crate) use types::*;
pub use types::{Deferred, TypedArrayType};

mod functions;
mod types;
Expand Down
22 changes: 22 additions & 0 deletions crates/neon-runtime/src/napi/bindings/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ pub type CallbackInfo = *mut CallbackInfo__;
pub struct EscapableHandleScope__ {
_unused: [u8; 0],
}

pub type EscapableHandleScope = *mut EscapableHandleScope__;

#[repr(C)]
Expand Down Expand Up @@ -203,3 +204,24 @@ impl std::ops::BitAndAssign for KeyFilter {
self.0 &= rhs.0;
}
}

#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct AsyncWork__ {
_unused: [u8; 0],
}

pub type AsyncWork = *mut AsyncWork__;

pub type AsyncExecuteCallback = Option<unsafe extern "C" fn(env: Env, data: *mut c_void)>;

pub type AsyncCompleteCallback =
Option<unsafe extern "C" fn(env: Env, status: Status, data: *mut c_void)>;

#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct Deferred__ {
_unused: [u8; 0],
}

pub type Deferred = *mut Deferred__;
22 changes: 22 additions & 0 deletions crates/neon-runtime/src/napi/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
pub mod array;
pub mod arraybuffer;
pub mod async_work;
pub mod buffer;
pub mod call;
pub mod convert;
Expand All @@ -13,6 +14,7 @@ pub mod lifecycle;
pub mod mem;
pub mod object;
pub mod primitive;
pub mod promise;
pub mod raw;
pub mod reference;
pub mod scope;
Expand All @@ -23,4 +25,24 @@ pub mod tsfn;
pub mod typedarray;

mod bindings;

pub use bindings::*;

use std::mem::MaybeUninit;

unsafe fn string(env: Env, s: impl AsRef<str>) -> raw::Local {
let s = s.as_ref();
let mut result = MaybeUninit::uninit();

assert_eq!(
create_string_utf8(
env,
s.as_bytes().as_ptr() as *const _,
s.len(),
result.as_mut_ptr(),
),
Status::Ok,
);

result.assume_init()
}
43 changes: 43 additions & 0 deletions crates/neon-runtime/src/napi/promise.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
use std::mem::MaybeUninit;
use std::ptr;

use crate::napi::bindings as napi;
use crate::raw::Env;

pub unsafe fn create(env: Env) -> (napi::Deferred, napi::Value) {
let mut deferred = MaybeUninit::uninit();
let mut promise = MaybeUninit::uninit();

assert_eq!(
napi::create_promise(env, deferred.as_mut_ptr(), promise.as_mut_ptr()),
napi::Status::Ok,
);

(deferred.assume_init(), promise.assume_init())
}

pub unsafe fn resolve(env: Env, deferred: napi::Deferred, resolution: napi::Value) {
assert_eq!(
napi::resolve_deferred(env, deferred, resolution),
napi::Status::Ok,
);
}

pub unsafe fn reject(env: Env, deferred: napi::Deferred, rejection: napi::Value) {
assert_eq!(
napi::reject_deferred(env, deferred, rejection),
napi::Status::Ok,
);
}

pub unsafe fn reject_err_message(env: Env, deferred: napi::Deferred, msg: impl AsRef<str>) {
let msg = super::string(env, msg);
let mut err = MaybeUninit::uninit();

assert_eq!(
napi::create_error(env, ptr::null_mut(), msg, err.as_mut_ptr()),
napi::Status::Ok,
);

reject(env, deferred, err.assume_init());
}
10 changes: 10 additions & 0 deletions crates/neon-runtime/src/napi/tag.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,3 +99,13 @@ pub unsafe fn is_date(env: Env, val: Local) -> bool {
);
result
}

/// Is `val` a Promise?
pub unsafe fn is_promise(env: Env, val: Local) -> bool {
let mut result = false;
assert_eq!(
napi::is_promise(env, val, &mut result as *mut _),
napi::Status::Ok
);
result
}
21 changes: 2 additions & 19 deletions crates/neon-runtime/src/napi/tsfn.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,7 @@ use std::mem::MaybeUninit;
use std::sync::{Arc, Mutex};

use crate::napi::bindings as napi;
use crate::raw::{Env, Local};

unsafe fn string(env: Env, s: impl AsRef<str>) -> Local {
let s = s.as_ref();
let mut result = MaybeUninit::uninit();

assert_eq!(
napi::create_string_utf8(
env,
s.as_bytes().as_ptr() as *const _,
s.len(),
result.as_mut_ptr(),
),
napi::Status::Ok,
);

result.assume_init()
}
use crate::raw::Env;

#[derive(Debug)]
struct Tsfn(napi::ThreadsafeFunction);
Expand Down Expand Up @@ -85,7 +68,7 @@ impl<T: Send + 'static> ThreadsafeFunction<T> {
env,
std::ptr::null_mut(),
std::ptr::null_mut(),
string(env, "neon threadsafe function"),
super::string(env, "neon threadsafe function"),
max_queue_size,
// Always set the reference count to 1. Prefer using
// Rust `Arc` to maintain the struct.
Expand Down
26 changes: 25 additions & 1 deletion src/context/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,8 @@ use crate::borrow::{Borrow, BorrowMut, Ref, RefMut};
use crate::context::internal::Env;
#[cfg(all(feature = "napi-4", feature = "channel-api"))]
use crate::event::Channel;
#[cfg(all(feature = "napi-1", feature = "task-api"))]
use crate::event::TaskBuilder;
use crate::handle::{Handle, Managed};
#[cfg(all(feature = "napi-6", feature = "channel-api"))]
use crate::lifecycle::InstanceData;
Expand All @@ -169,6 +171,8 @@ pub use crate::types::buffer::lock::Lock;
#[cfg(feature = "napi-5")]
use crate::types::date::{DateError, JsDate};
use crate::types::error::JsError;
#[cfg(all(feature = "napi-1", feature = "promise-api"))]
use crate::types::{Deferred, JsPromise};
use crate::types::{
JsArray, JsArrayBuffer, JsBoolean, JsBuffer, JsFunction, JsNull, JsNumber, JsObject, JsString,
JsUndefined, JsValue, StringResult, Value,
Expand Down Expand Up @@ -608,6 +612,23 @@ pub trait Context<'a>: ContextInternal<'a> {
fn queue(&mut self) -> Channel {
self.channel()
}

#[cfg(all(feature = "napi-1", feature = "promise-api"))]
#[cfg_attr(docsrs, doc(cfg(feature = "promise-api")))]
fn promise(&mut self) -> (Deferred, Handle<'a, JsPromise>) {
JsPromise::new(self)
}

#[cfg(all(feature = "napi-1", feature = "task-api"))]
#[cfg_attr(docsrs, doc(cfg(feature = "task-api")))]
fn task<'cx, O, E>(&'cx mut self, execute: E) -> TaskBuilder<Self, E>
where
'a: 'cx,
O: Send + 'static,
E: FnOnce() -> O + Send + 'static,
{
TaskBuilder::new(self, execute)
}
}

/// An execution context of module initialization.
Expand Down Expand Up @@ -851,7 +872,10 @@ impl<'a> TaskContext<'a> {
Scope::with(env, |scope| f(TaskContext { scope }))
}

#[cfg(all(feature = "napi-4", feature = "channel-api"))]
#[cfg(any(
all(feature = "napi-1", feature = "task-api"),
all(feature = "napi-4", feature = "channel-api"),
))]
pub(crate) fn with_context<T, F: for<'b> FnOnce(TaskContext<'b>) -> T>(env: Env, f: F) -> T {
Scope::with(env, |scope| f(TaskContext { scope }))
}
Expand Down
Loading

0 comments on commit 0803489

Please sign in to comment.