From 33bfe08af008a407ec96cc6668e49fa46dbc7b71 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Wed, 28 Aug 2019 09:44:45 -0700 Subject: [PATCH] Default all async support to `std::future` This commit defaults all crates in-tree to use `std::future` by default and none of them support the crates.io `futures` 0.1 crate any more. This is a breaking change for `wasm-bindgen-futures` and `wasm-bindgen-test` so they've both received a major version bump to reflect the new defaults. Historical versions of these crates should continue to work if necessary, but they won't receive any more maintenance after this is merged. The movement here liberally uses `async`/`await` to remove the need for using any combinators on the `Future` trait. As a result many of the crates now rely on a much more recent version of the compiler, especially to run tests. The `wasm-bindgen-futures` crate was updated to remove all of its futures-related dependencies and purely use `std::future`, hopefully improving its compatibility by not having any version compat considerations over time. The implementations of the executors here are relatively simple and only delve slightly into the `RawWaker` business since there are no other stable APIs in `std::task` for wrapping these. This commit also adds support for: #[wasm_bindgen_test] async fn foo() { // ... } where previously you needed to pass `(async)` now that's inferred because it's an `async fn`. Closes #1558 Closes #1695 --- Cargo.toml | 2 +- azure-pipelines.yml | 19 +- crates/futures/Cargo.toml | 11 +- crates/futures/src/futures_0_3.rs | 274 ------------------ crates/futures/src/legacy.rs | 204 ------------- crates/futures/src/legacy_atomics.rs | 166 ----------- crates/futures/src/lib.rs | 94 +----- crates/futures/src/multithread.rs | 184 ++++++++++++ .../src/{legacy_shared.rs => shared.rs} | 84 +++--- crates/futures/src/singlethread.rs | 142 +++++++++ crates/js-sys/Cargo.toml | 5 +- crates/js-sys/tests/wasm/WebAssembly.rs | 54 ++-- crates/js-sys/tests/wasm/main.rs | 6 - crates/test-macro/Cargo.toml | 6 +- crates/test-macro/src/lib.rs | 3 + crates/test/Cargo.toml | 7 +- crates/test/sample/Cargo.toml | 2 +- crates/test/sample/src/lib.rs | 29 +- crates/test/sample/tests/browser.rs | 5 - crates/test/sample/tests/common/mod.rs | 36 +-- crates/test/sample/tests/node.rs | 5 - crates/test/src/rt/mod.rs | 85 +++--- crates/web-sys/Cargo.toml | 5 +- crates/web-sys/tests/wasm/event.rs | 33 +-- crates/web-sys/tests/wasm/main.rs | 7 - crates/web-sys/tests/wasm/response.rs | 24 +- .../wasm/rtc_rtp_transceiver_direction.rs | 84 ++---- .../wasm/whitelisted_immutable_slices.rs | 1 - examples/fetch/Cargo.toml | 3 +- examples/fetch/src/lib.rs | 42 ++- examples/raytrace-parallel/Cargo.toml | 4 +- examples/raytrace-parallel/src/lib.rs | 13 +- 32 files changed, 591 insertions(+), 1048 deletions(-) delete mode 100644 crates/futures/src/futures_0_3.rs delete mode 100644 crates/futures/src/legacy.rs delete mode 100644 crates/futures/src/legacy_atomics.rs create mode 100644 crates/futures/src/multithread.rs rename crates/futures/src/{legacy_shared.rs => shared.rs} (54%) create mode 100644 crates/futures/src/singlethread.rs diff --git a/Cargo.toml b/Cargo.toml index a0a2c035189..c5730add5cc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -43,7 +43,7 @@ cfg-if = "0.1.9" [target.'cfg(target_arch = "wasm32")'.dev-dependencies] js-sys = { path = 'crates/js-sys', version = '0.3.27' } -wasm-bindgen-test = { path = 'crates/test', version = '=0.2.50' } +wasm-bindgen-test = { path = 'crates/test', version = '=0.3.0' } serde_derive = "1.0" wasm-bindgen-test-crate-a = { path = 'tests/crates/a', version = '0.1' } wasm-bindgen-test-crate-b = { path = 'tests/crates/b', version = '0.1' } diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 539ffb99249..fe7aa1e2cda 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -9,6 +9,8 @@ jobs: displayName: "Run wasm-bindgen crate tests (unix)" steps: - template: ci/azure-install-rust.yml + parameters: + toolchain: nightly - template: ci/azure-install-node.yml - template: ci/azure-install-geckodriver.yml - template: ci/azure-install-sccache.yml @@ -48,6 +50,8 @@ jobs: vmImage: vs2017-win2016 steps: - template: ci/azure-install-rust.yml + parameters: + toolchain: nightly - template: ci/azure-install-node.yml - template: ci/azure-install-geckodriver.yml - template: ci/azure-install-sccache.yml @@ -91,6 +95,8 @@ jobs: displayName: "Run web-sys crate tests" steps: - template: ci/azure-install-rust.yml + parameters: + toolchain: nightly - template: ci/azure-install-node.yml - template: ci/azure-install-geckodriver.yml - template: ci/azure-install-sccache.yml @@ -104,6 +110,8 @@ jobs: displayName: "Run js-sys crate tests" steps: - template: ci/azure-install-rust.yml + parameters: + toolchain: nightly - template: ci/azure-install-node.yml - template: ci/azure-install-geckodriver.yml - template: ci/azure-install-sccache.yml @@ -113,6 +121,8 @@ jobs: displayName: "Run wasm-bindgen-webidl crate tests" steps: - template: ci/azure-install-rust.yml + parameters: + toolchain: nightly - template: ci/azure-install-node.yml #- template: ci/azure-install-sccache.yml - script: cargo test -p wasm-bindgen-webidl @@ -125,7 +135,7 @@ jobs: steps: - template: ci/azure-install-rust.yml parameters: - toolchain: beta + toolchain: nightly - template: ci/azure-install-node.yml - template: ci/azure-install-sccache.yml - script: cargo test -p wasm-bindgen-macro @@ -156,6 +166,8 @@ jobs: displayName: "Build almost all examples" steps: - template: ci/azure-install-rust.yml + parameters: + toolchain: nightly - template: ci/azure-install-sccache.yml - template: ci/azure-install-wasm-pack.yml - script: mv _package.json package.json && npm install && rm package.json @@ -198,6 +210,8 @@ jobs: displayName: "Build benchmarks" steps: - template: ci/azure-install-rust.yml + parameters: + toolchain: nightly - template: ci/azure-install-sccache.yml - template: ci/azure-install-wasm-pack.yml - script: wasm-pack build --target web benchmarks @@ -263,7 +277,6 @@ jobs: - job: doc_book displayName: "Doc - build the book" steps: - - template: ci/azure-install-rust.yml - script: | set -e curl -L https://github.com/rust-lang-nursery/mdBook/releases/download/v0.3.0/mdbook-v0.3.0-x86_64-unknown-linux-gnu.tar.gz | tar xzf - @@ -279,6 +292,8 @@ jobs: displayName: "Doc - build the API documentation" steps: - template: ci/azure-install-rust.yml + parameters: + toolchain: nightly # Install rustfmt so we can format the web-sys bindings - script: rustup component add rustfmt displayName: "Install rustfmt" diff --git a/crates/futures/Cargo.toml b/crates/futures/Cargo.toml index a0d99c2a788..28640617d52 100644 --- a/crates/futures/Cargo.toml +++ b/crates/futures/Cargo.toml @@ -7,17 +7,13 @@ license = "MIT/Apache-2.0" name = "wasm-bindgen-futures" repository = "https://github.com/rustwasm/wasm-bindgen/tree/master/crates/futures" readme = "./README.md" -version = "0.3.27" +version = "0.4.0" edition = "2018" [dependencies] cfg-if = "0.1.9" -futures = "0.1.20" js-sys = { path = "../js-sys", version = '0.3.27' } wasm-bindgen = { path = "../..", version = '0.2.50' } -futures-util-preview = { version = "0.3.0-alpha.18", optional = true } -futures-channel-preview = { version = "0.3.0-alpha.18", optional = true } -lazy_static = { version = "1.3.0", optional = true } [target.'cfg(target_feature = "atomics")'.dependencies.web-sys] path = "../web-sys" @@ -28,7 +24,4 @@ features = [ ] [target.'cfg(target_arch = "wasm32")'.dev-dependencies] -wasm-bindgen-test = { path = '../test', version = '0.2.50' } - -[features] -futures_0_3 = ["futures-util-preview", "futures-channel-preview", "lazy_static"] +wasm-bindgen-test = { path = '../test', version = '0.3.0' } diff --git a/crates/futures/src/futures_0_3.rs b/crates/futures/src/futures_0_3.rs deleted file mode 100644 index 79fe46cd63b..00000000000 --- a/crates/futures/src/futures_0_3.rs +++ /dev/null @@ -1,274 +0,0 @@ -use std::cell::{Cell, RefCell}; -use std::collections::VecDeque; -use std::fmt; -use std::future::Future; -use std::pin::Pin; -use std::rc::Rc; -use std::sync::Arc; -use std::task::{Context, Poll}; - -use futures_channel::oneshot; -use futures_util::future::FutureExt; -use futures_util::task::ArcWake; - -use lazy_static::lazy_static; - -use js_sys::Promise; -use wasm_bindgen::prelude::*; - -/// A Rust `Future` backed by a JavaScript `Promise`. -/// -/// This type is constructed with a JavaScript `Promise` object and translates -/// it to a Rust `Future`. This type implements the `Future` trait from the -/// `futures` crate and will either succeed or fail depending on what happens -/// with the JavaScript `Promise`. -/// -/// Currently this type is constructed with `JsFuture::from`. -pub struct JsFuture { - rx: oneshot::Receiver>, -} - -impl fmt::Debug for JsFuture { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "JsFuture {{ ... }}") - } -} - -impl From for JsFuture { - fn from(js: Promise) -> JsFuture { - // See comments in `src/lib.rs` for why we're using one self-contained - // callback here. - let (tx, rx) = oneshot::channel(); - let state = Rc::new(RefCell::new(None)); - let state2 = state.clone(); - let resolve = Closure::once(move |val| finish(&state2, Ok(val))); - let state2 = state.clone(); - let reject = Closure::once(move |val| finish(&state2, Err(val))); - - js.then2(&resolve, &reject); - *state.borrow_mut() = Some((tx, resolve, reject)); - - return JsFuture { rx }; - - fn finish( - state: &RefCell< - Option<( - oneshot::Sender>, - Closure, - Closure, - )>, - >, - val: Result, - ) { - match state.borrow_mut().take() { - // We don't have any guarantee that anyone's still listening at this - // point (the Rust `JsFuture` could have been dropped) so simply - // ignore any errors here. - Some((tx, _, _)) => drop(tx.send(val)), - None => wasm_bindgen::throw_str("cannot finish twice"), - } - } - } -} - -impl Future for JsFuture { - type Output = Result; - - fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll { - match self.rx.poll_unpin(cx) { - Poll::Pending => Poll::Pending, - Poll::Ready(Ok(val)) => Poll::Ready(val), - Poll::Ready(Err(_)) => wasm_bindgen::throw_str("cannot cancel"), - } - } -} - -/// Converts a Rust `Future` into a JavaScript `Promise`. -/// -/// This function will take any future in Rust and schedule it to be executed, -/// returning a JavaScript `Promise` which can then be passed back to JavaScript -/// to get plumbed into the rest of a system. -/// -/// The `future` provided must adhere to `'static` because it'll be scheduled -/// to run in the background and cannot contain any stack references. The -/// returned `Promise` will be resolved or rejected when the future completes, -/// depending on whether it finishes with `Ok` or `Err`. -/// -/// # Panics -/// -/// Note that in wasm panics are currently translated to aborts, but "abort" in -/// this case means that a JavaScript exception is thrown. The wasm module is -/// still usable (likely erroneously) after Rust panics. -/// -/// If the `future` provided panics then the returned `Promise` **will not -/// resolve**. Instead it will be a leaked promise. This is an unfortunate -/// limitation of wasm currently that's hoped to be fixed one day! -pub fn future_to_promise(future: F) -> Promise -where - F: Future> + 'static, -{ - let mut future = Some(future); - - Promise::new(&mut |resolve, reject| { - // TODO change Promise::new to be FnOnce - spawn_local(future.take().unwrap_throw().map(move |val| match val { - Ok(val) => { - resolve.call1(&JsValue::undefined(), &val).unwrap_throw(); - } - Err(val) => { - reject.call1(&JsValue::undefined(), &val).unwrap_throw(); - } - })); - }) -} - -/// Runs a Rust `Future` on a local task queue. -/// -/// The `future` provided must adhere to `'static` because it'll be scheduled -/// to run in the background and cannot contain any stack references. -/// -/// # Panics -/// -/// This function has the same panic behavior as `future_to_promise`. -pub fn spawn_local(future: F) -where - F: Future + 'static, -{ - struct Task { - // This is an Option so that the Future can be immediately dropped when it's finished - future: RefCell + 'static>>>>, - - // This is used to ensure that the Task will only be queued once - is_queued: Cell, - } - - // TODO This is only safe because JS is currently single-threaded - unsafe impl Send for Task {} - unsafe impl Sync for Task {} - - impl Task { - #[inline] - fn new(future: F) -> Arc - where - F: Future + 'static, - { - Arc::new(Self { - future: RefCell::new(Some(Box::pin(future))), - is_queued: Cell::new(false), - }) - } - } - - impl ArcWake for Task { - fn wake_by_ref(arc_self: &Arc) { - // This ensures that it's only queued once - if arc_self.is_queued.replace(true) { - return; - } - - let mut lock = EXECUTOR.tasks.borrow_mut(); - - lock.push_back(arc_self.clone()); - - // The Task will be polled on the next microtask event tick - EXECUTOR.next_tick.schedule(); - } - } - - struct NextTick { - is_spinning: Cell, - promise: Promise, - closure: Closure, - } - - impl NextTick { - #[inline] - fn new(mut f: F) -> Self - where - F: FnMut() + 'static, - { - Self { - is_spinning: Cell::new(false), - promise: Promise::resolve(&JsValue::null()), - closure: Closure::wrap(Box::new(move |_| { - f(); - })), - } - } - - fn schedule(&self) { - // This ensures that it's only scheduled once - if self.is_spinning.replace(true) { - return; - } - - // TODO avoid creating a new Promise - self.promise.then(&self.closure); - } - - fn done(&self) { - self.is_spinning.set(false); - } - } - - struct Executor { - // This is a queue of Tasks which will be polled in order - tasks: RefCell>>, - - // This is used to ensure that Tasks are polled on the next microtask event tick - next_tick: NextTick, - } - - // TODO This is only safe because JS is currently single-threaded - unsafe impl Send for Executor {} - unsafe impl Sync for Executor {} - - lazy_static! { - static ref EXECUTOR: Executor = Executor { - tasks: RefCell::new(VecDeque::new()), - - // This closure will only be called on the next microtask event tick - next_tick: NextTick::new(|| { - let tasks = &EXECUTOR.tasks; - - loop { - let mut lock = tasks.borrow_mut(); - - match lock.pop_front() { - Some(task) => { - let mut borrow = task.future.borrow_mut(); - - // This will only be None if the Future wakes up the Waker after returning Poll::Ready - if let Some(future) = borrow.as_mut() { - let poll = { - // Clear `is_queued` flag so that it will re-queue if poll calls waker.wake() - task.is_queued.set(false); - - // This is necessary because the polled task might queue more tasks - drop(lock); - - // TODO is there some way of saving these so they don't need to be recreated all the time ? - let waker = futures_util::task::waker(task.clone()); - let cx = &mut Context::from_waker(&waker); - Pin::new(future).poll(cx) - }; - - if let Poll::Ready(_) = poll { - // Cleanup the Future immediately - *borrow = None; - } - } - }, - None => { - // All of the Tasks have been polled, so it's now possible to schedule the NextTick again - EXECUTOR.next_tick.done(); - break; - }, - } - } - }), - }; - } - - ArcWake::wake_by_ref(&Task::new(future)); -} diff --git a/crates/futures/src/legacy.rs b/crates/futures/src/legacy.rs deleted file mode 100644 index a200d088c60..00000000000 --- a/crates/futures/src/legacy.rs +++ /dev/null @@ -1,204 +0,0 @@ -use futures::executor::{self, Notify, Spawn}; -use futures::prelude::*; -use js_sys::{Function, Promise}; -use std::cell::{Cell, RefCell}; -use std::rc::Rc; -use std::sync::Arc; -use wasm_bindgen::prelude::*; - -/// Converts a Rust `Future` into a JavaScript `Promise`. -/// -/// This function will take any future in Rust and schedule it to be executed, -/// returning a JavaScript `Promise` which can then be passed back to JavaScript -/// to get plumbed into the rest of a system. -/// -/// The `future` provided must adhere to `'static` because it'll be scheduled -/// to run in the background and cannot contain any stack references. The -/// returned `Promise` will be resolved or rejected when the future completes, -/// depending on whether it finishes with `Ok` or `Err`. -/// -/// # Panics -/// -/// Note that in wasm panics are currently translated to aborts, but "abort" in -/// this case means that a JavaScript exception is thrown. The wasm module is -/// still usable (likely erroneously) after Rust panics. -/// -/// If the `future` provided panics then the returned `Promise` **will not -/// resolve**. Instead it will be a leaked promise. This is an unfortunate -/// limitation of wasm currently that's hoped to be fixed one day! -pub fn future_to_promise(future: F) -> Promise -where - F: Future + 'static, -{ - _future_to_promise(Box::new(future)) -} - -// Implementation of actually transforming a future into a JavaScript `Promise`. -// -// The only primitive we have to work with here is `Promise::new`, which gives -// us two callbacks that we can use to either reject or resolve the promise. -// It's our job to ensure that one of those callbacks is called at the -// appropriate time. -// -// Now we know that JavaScript (in general) can't block and is largely -// notification/callback driven. That means that our future must either have -// synchronous computational work to do, or it's "scheduled a notification" to -// happen. These notifications are likely callbacks to get executed when things -// finish (like a different promise or something like `setTimeout`). The general -// idea here is thus to do as much synchronous work as we can and then otherwise -// translate notifications of a future's task into "let's poll the future!" -// -// This isn't necessarily the greatest future executor in the world, but it -// should get the job done for now hopefully. -fn _future_to_promise(future: Box>) -> Promise { - let mut future = Some(executor::spawn(future)); - return Promise::new(&mut |resolve, reject| { - Package::poll(&Arc::new(Package { - spawn: RefCell::new(future.take().unwrap()), - resolve, - reject, - notified: Cell::new(State::Notified), - })); - }); - - struct Package { - // Our "spawned future". This'll have everything we need to poll the - // future and continue to move it forward. - spawn: RefCell>>>, - - // The current state of this future, expressed in an enum below. This - // indicates whether we're currently polling the future, received a - // notification and need to keep polling, or if we're waiting for a - // notification to come in (and no one is polling). - notified: Cell, - - // Our two callbacks connected to the `Promise` that we returned to - // JavaScript. We'll be invoking one of these at the end. - resolve: Function, - reject: Function, - } - - // The possible states our `Package` (future) can be in, tracked internally - // and used to guide what happens when polling a future. - enum State { - // This future is currently and actively being polled. Attempting to - // access the future will result in a runtime panic and is considered a - // bug. - Polling, - - // This future has been notified, while it was being polled. This marker - // is used in the `Notify` implementation below, and indicates that a - // notification was received that the future is ready to make progress. - // If seen, however, it probably means that the future is also currently - // being polled. - Notified, - - // The future is blocked, waiting for something to happen. Stored here - // is a self-reference to the future itself so we can pull it out in - // `Notify` and continue polling. - // - // Note that the self-reference here is an Arc-cycle that will leak - // memory unless the future completes, but currently that should be ok - // as we'll have to stick around anyway while the future is executing! - // - // This state is removed as soon as a notification comes in, so the leak - // should only be "temporary" - Waiting(Arc), - } - - // No shared memory right now, wasm is single threaded, no need to worry - // about this! - unsafe impl Send for Package {} - unsafe impl Sync for Package {} - - impl Package { - // Move the future contained in `me` as far forward as we can. This will - // do as much synchronous work as possible to complete the future, - // ensuring that when it blocks we're scheduled to get notified via some - // callback somewhere at some point (vague, right?) - // - // TODO: this probably shouldn't do as much synchronous work as possible - // as it can starve other computations. Rather it should instead - // yield every so often with something like `setTimeout` with the - // timeout set to zero. - fn poll(me: &Arc) { - loop { - match me.notified.replace(State::Polling) { - // We received a notification while previously polling, or - // this is the initial poll. We've got work to do below! - State::Notified => {} - - // We've gone through this loop once and no notification was - // received while we were executing work. That means we got - // `NotReady` below and we're scheduled to receive a - // notification. Block ourselves and wait for later. - // - // When the notification comes in it'll notify our task, see - // our `Waiting` state, and resume the polling process - State::Polling => { - me.notified.set(State::Waiting(me.clone())); - break; - } - - State::Waiting(_) => panic!("shouldn't see waiting state!"), - } - - let (val, f) = match me.spawn.borrow_mut().poll_future_notify(me, 0) { - // If the future is ready, immediately call the - // resolve/reject callback and then return as we're done. - Ok(Async::Ready(value)) => (value, &me.resolve), - Err(value) => (value, &me.reject), - - // Otherwise keep going in our loop, if we weren't notified - // we'll break out and start waiting. - Ok(Async::NotReady) => continue, - }; - - drop(f.call1(&JsValue::undefined(), &val)); - break; - } - } - } - - impl Notify for Package { - fn notify(&self, _id: usize) { - let me = match self.notified.replace(State::Notified) { - // we need to schedule polling to resume, so keep going - State::Waiting(me) => me, - - // we were already notified, and were just notified again; - // having now coalesced the notifications we return as it's - // still someone else's job to process this - State::Notified => return, - - // the future was previously being polled, and we've just - // switched it to the "you're notified" state. We don't have - // access to the future as it's being polled, so the future - // polling process later sees this notification and will - // continue polling. For us, though, there's nothing else to do, - // so we bail out. - // later see - State::Polling => return, - }; - - // Use `Promise.then` on a resolved promise to place our execution - // onto the next turn of the microtask queue, enqueueing our poll - // operation. We don't currently poll immediately as it turns out - // `futures` crate adapters aren't compatible with it and it also - // helps avoid blowing the stack by accident. - // - // Note that the `Rc`/`RefCell` trick here is basically to just - // ensure that our `Closure` gets cleaned up appropriately. - let promise = Promise::resolve(&JsValue::undefined()); - let slot = Rc::new(RefCell::new(None)); - let slot2 = slot.clone(); - let closure = Closure::wrap(Box::new(move |_| { - let myself = slot2.borrow_mut().take(); - debug_assert!(myself.is_some()); - Package::poll(&me); - }) as Box); - promise.then(&closure); - *slot.borrow_mut() = Some(closure); - } - } -} diff --git a/crates/futures/src/legacy_atomics.rs b/crates/futures/src/legacy_atomics.rs deleted file mode 100644 index b09e8b7ced5..00000000000 --- a/crates/futures/src/legacy_atomics.rs +++ /dev/null @@ -1,166 +0,0 @@ -use futures::executor::{self, Notify, Spawn}; -use futures::prelude::*; -use js_sys::Function; -use std::sync::atomic::{AtomicI32, Ordering}; -use std::sync::Arc; -use wasm_bindgen::prelude::*; -use wasm_bindgen::JsCast; - -// Duplicate a bit here because `then` takes a `JsValue` instead of a `Closure`. -#[wasm_bindgen] -extern "C" { - type Promise; - #[wasm_bindgen(method)] - fn then(this: &Promise, cb: &JsValue) -> Promise; - - type Atomics; - #[wasm_bindgen(static_method_of = Atomics, js_name = waitAsync)] - fn wait_async(buf: &JsValue, index: i32, value: i32) -> js_sys::Promise; - #[wasm_bindgen(static_method_of = Atomics, js_name = waitAsync, getter)] - fn get_wait_async() -> JsValue; -} - -/// Converts a Rust `Future` into a JavaScript `Promise`. -/// -/// This function will take any future in Rust and schedule it to be executed, -/// returning a JavaScript `Promise` which can then be passed back to JavaScript -/// to get plumbed into the rest of a system. -/// -/// The `future` provided must adhere to `'static` because it'll be scheduled -/// to run in the background and cannot contain any stack references. The -/// returned `Promise` will be resolved or rejected when the future completes, -/// depending on whether it finishes with `Ok` or `Err`. -/// -/// # Panics -/// -/// Note that in wasm panics are currently translated to aborts, but "abort" in -/// this case means that a JavaScript exception is thrown. The wasm module is -/// still usable (likely erroneously) after Rust panics. -/// -/// If the `future` provided panics then the returned `Promise` **will not -/// resolve**. Instead it will be a leaked promise. This is an unfortunate -/// limitation of wasm currently that's hoped to be fixed one day! -pub fn future_to_promise(future: F) -> js_sys::Promise -where - F: Future + 'static, -{ - _future_to_promise(Box::new(future)) -} - -// Implementation of actually transforming a future into a JavaScript `Promise`. -// -// The main primitives used here are `Promise::new` to actually create a JS -// promise to return as well as `Atomics.waitAsync` to create a promise that we -// can asynchronously wait on. The general idea here is that we'll create a -// promise to return and schedule work to happen in `Atomics.waitAsync` -// callbacks. -// -// After we've created a promise we start polling a future, and whenever it's -// not ready we'll execute `Atomics.waitAsync`. When that resolves we'll keep -// polling the future, and this happens until the future is done. Finally -// when it's all finished we call either resolver or reject depending on the -// result of the future. -fn _future_to_promise(future: Box>) -> js_sys::Promise { - let mut future = Some(executor::spawn(future)); - return js_sys::Promise::new(&mut |resolve, reject| { - Package { - spawn: future.take().unwrap(), - resolve, - reject, - waker: Arc::new(Waker { - value: AtomicI32::new(1), // 1 == "notified, ready to poll" - }), - } - .poll(); - }); - - struct Package { - // Our "spawned future". This'll have everything we need to poll the - // future and continue to move it forward. - spawn: Spawn>>, - - // Our two callbacks connected to the `Promise` that we returned to - // JavaScript. We'll be invoking one of these at the end. - resolve: Function, - reject: Function, - - // Shared state used to communicate waking up this future, this is the - // `Send + Sync` piece needed by the async task system. - waker: Arc, - } - - struct Waker { - value: AtomicI32, - }; - - impl Notify for Waker { - fn notify(&self, _id: usize) { - // Attempt to notify us by storing 1. If we're already 1 then we - // were previously notified and there's nothing to do. Otherwise - // we execute the native `notify` instruction to wake up the - // corresponding `waitAsync` that was waiting for the transition - // from 0 to 1. - let prev = self.value.swap(1, Ordering::SeqCst); - if prev == 1 { - return; - } - debug_assert_eq!(prev, 0); - unsafe { - core::arch::wasm32::atomic_notify( - &self.value as *const AtomicI32 as *mut i32, - 1, // number of threads to notify - ); - } - } - } - - impl Package { - fn poll(mut self) { - // Poll in a loop waiting for the future to become ready. Note that - // we probably shouldn't maximize synchronous work here but rather - // we should occasionally yield back to the runtime and schedule - // ourselves to resume this future later on. - // - // Note that 0 here means "need a notification" and 1 means "we got - // a notification". That means we're storing 0 into the `notified` - // slot and we're trying to read 1 to keep on going. - while self.waker.value.swap(0, Ordering::SeqCst) == 1 { - let (val, f) = match self.spawn.poll_future_notify(&self.waker, 0) { - // If the future is ready, immediately call the - // resolve/reject callback and then return as we're done. - Ok(Async::Ready(value)) => (value, &self.resolve), - Err(value) => (value, &self.reject), - - // ... otherwise let's break out and wait - Ok(Async::NotReady) => break, - }; - - // Call the resolution function, and then when we're done - // destroy ourselves through `drop` since our future is no - // longer needed. - drop(f.call1(&JsValue::undefined(), &val)); - return; - } - - // Create a `js_sys::Promise` using `Atomics.waitAsync` (or our - // polyfill) and then register its completion callback as simply - // calling this function again. - let promise = wait_async(&self.waker.value, 0).unchecked_into::(); - let closure = Closure::once_into_js(move || { - self.poll(); - }); - promise.then(&closure); - } - } -} - -fn wait_async(ptr: &AtomicI32, val: i32) -> js_sys::Promise { - // If `Atomics.waitAsync` isn't defined (as it isn't defined anywhere today) - // then we use our fallback, otherwise we use the native function. - if Atomics::get_wait_async().is_undefined() { - crate::wait_async_polyfill::wait_async(ptr, val) - } else { - let mem = wasm_bindgen::memory().unchecked_into::(); - Atomics::wait_async(&mem.buffer(), ptr as *const AtomicI32 as i32 / 4, val) - } -} diff --git a/crates/futures/src/lib.rs b/crates/futures/src/lib.rs index 57da7b365d0..bcb15c60072 100644 --- a/crates/futures/src/lib.rs +++ b/crates/futures/src/lib.rs @@ -31,104 +31,22 @@ //! running tasks on the next tick of the micro task queue. The futures built on //! top of it can be scheduled for execution by conversion into a JavaScript //! `Promise`. -//! -//! ```rust,no_run -//! extern crate futures; -//! extern crate js_sys; -//! extern crate wasm_bindgen; -//! extern crate wasm_bindgen_futures; -//! -//! use futures::{Async, Future, Poll}; -//! use wasm_bindgen::prelude::*; -//! use wasm_bindgen_futures::{JsFuture, future_to_promise}; -//! -//! /// A future that becomes ready after a tick of the micro task queue. -//! pub struct NextTick { -//! inner: JsFuture, -//! } -//! -//! impl NextTick { -//! /// Construct a new `NextTick` future. -//! pub fn new() -> NextTick { -//! // Create a resolved promise that will run its callbacks on the next -//! // tick of the micro task queue. -//! let promise = js_sys::Promise::resolve(&JsValue::NULL); -//! // Convert the promise into a `JsFuture`. -//! let inner = JsFuture::from(promise); -//! NextTick { inner } -//! } -//! } -//! -//! impl Future for NextTick { -//! type Item = (); -//! type Error = (); -//! -//! fn poll(&mut self) -> Poll<(), ()> { -//! // Polling a `NextTick` just forwards to polling if the inner promise is -//! // ready. -//! match self.inner.poll() { -//! Ok(Async::Ready(_)) => Ok(Async::Ready(())), -//! Ok(Async::NotReady) => Ok(Async::NotReady), -//! Err(_) => unreachable!( -//! "We only create NextTick with a resolved inner promise, never \ -//! a rejected one, so we can't get an error here" -//! ), -//! } -//! } -//! } -//! -//! /// Export a function to JavaScript that does some work in the next tick of the -//! /// micro task queue! -//! #[wasm_bindgen] -//! pub fn schedule_some_work_for_next_tick() -> js_sys::Promise { -//! let future = NextTick::new() -//! // Do some work... -//! .and_then(|_| { -//! Ok(42) -//! }) -//! // And then convert the `Item` and `Error` into `JsValue`. -//! .map(|result| { -//! JsValue::from(result) -//! }) -//! .map_err(|error| { -//! let js_error = js_sys::Error::new(&format!("uh oh! {:?}", error)); -//! JsValue::from(js_error) -//! }); -//! -//! // Convert the `Future` into a JavaScript -//! // `Promise`! -//! future_to_promise(future) -//! } -//! ``` #![cfg_attr(target_feature = "atomics", feature(stdsimd))] #![deny(missing_docs)] use cfg_if::cfg_if; -mod legacy_shared; -pub use legacy_shared::*; +mod shared; +pub use shared::*; cfg_if! { if #[cfg(target_feature = "atomics")] { - /// Contains a thread-safe version of this crate, with Futures 0.1 - mod legacy_atomics; - pub use legacy_atomics::*; - - /// Polyfill for `Atomics.waitAsync` function mod wait_async_polyfill; + mod multithread; + pub use multithread::*; } else { - mod legacy; - pub use legacy::*; - } -} - -#[cfg(feature = "futures_0_3")] -cfg_if! { - if #[cfg(target_feature = "atomics")] { - compile_error!("futures 0.3 support is not implemented with atomics yet"); - } else { - /// Contains a Futures 0.3 implementation of this crate. - pub mod futures_0_3; + mod singlethread; + pub use singlethread::*; } } diff --git a/crates/futures/src/multithread.rs b/crates/futures/src/multithread.rs new file mode 100644 index 00000000000..79afcc71d23 --- /dev/null +++ b/crates/futures/src/multithread.rs @@ -0,0 +1,184 @@ +use js_sys::Promise; +use std::future::Future; +use std::mem::ManuallyDrop; +use std::pin::Pin; +use std::sync::atomic::{AtomicI32, Ordering::SeqCst}; +use std::sync::Arc; +use std::task::{Context, Poll, RawWaker, RawWakerVTable, Waker}; +use wasm_bindgen::prelude::*; +use wasm_bindgen::JsCast; + +// Duplicate a bit here because `then` takes a `JsValue` instead of a `Closure`. +#[wasm_bindgen] +extern "C" { + type MyPromise; + #[wasm_bindgen(method)] + fn then(this: &MyPromise, cb: &JsValue); + + type Atomics; + #[wasm_bindgen(static_method_of = Atomics, js_name = waitAsync)] + fn wait_async(buf: &JsValue, index: i32, value: i32) -> js_sys::Promise; + #[wasm_bindgen(static_method_of = Atomics, js_name = waitAsync, getter)] + fn get_wait_async() -> JsValue; +} + +pub use crate::shared::{spawn_local, JsFuture}; + +/// Converts a Rust `Future` into a JavaScript `Promise`. +/// +/// This function will take any future in Rust and schedule it to be executed, +/// returning a JavaScript `Promise` which can then be passed back to JavaScript +/// to get plumbed into the rest of a system. +/// +/// The `future` provided must adhere to `'static` because it'll be scheduled +/// to run in the background and cannot contain any stack references. The +/// returned `Promise` will be resolved or rejected when the future completes, +/// depending on whether it finishes with `Ok` or `Err`. +/// +/// # Panics +/// +/// Note that in wasm panics are currently translated to aborts, but "abort" in +/// this case means that a JavaScript exception is thrown. The wasm module is +/// still usable (likely erroneously) after Rust panics. +/// +/// If the `future` provided panics then the returned `Promise` **will not +/// resolve**. Instead it will be a leaked promise. This is an unfortunate +/// limitation of wasm currently that's hoped to be fixed one day! +pub fn future_to_promise(future: F) -> Promise +where + F: Future> + 'static, +{ + _future_to_promise(Box::new(future)) +} + +fn _future_to_promise(future: Box>>) -> Promise { + let mut future = Some(future); + js_sys::Promise::new(&mut move |resolve, reject| { + let future = match future.take() { + Some(f) => f, + None => wasm_bindgen::throw_str("cannot invoke twice"), + }; + let state = Arc::new(State { + value: AtomicI32::new(1), + }); + Package { + // Note that the unsafety should be ok here since we're always + // passing in valid pointers and we're handling cleanup with + // `Waker`. + waker: unsafe { Waker::from_raw(State::into_raw_waker(state.clone())) }, + state, + future: Pin::from(future), + resolve, + reject, + } + .poll(); + }) +} + +struct Package { + state: Arc, + future: Pin>>>, + resolve: js_sys::Function, + reject: js_sys::Function, + waker: Waker, +} + +struct State { + value: AtomicI32, +} + +impl Package { + fn poll(mut self) { + // Flag ourselves as ready to receive another notification. We should + // never enter this method unless our `value` is set to `1`, so assert + // that as well. + let prev = self.state.value.swap(0, SeqCst); + debug_assert_eq!(prev, 1); + + // Afterwards start making progress on the future by calling the `poll` + // function. If we get `Ready` then we simply invoke the appropriate JS + // function to resolve the JS `Promise` we're connected to. + // + // If `Pending` is received then we're guaranteed to eventually receive + // an `atomic.notify` as well as a store to `1` in `self.state.value`. + // In this case we create a new promise (using `Atomics.waitAsync`) and + // then we register that promise to continue polling when it's resolved. + // Note that if a `wake` happened while we were polling or after we see + // `Pending` then the promise should end up resolving immediately due to + // the atomicity of `Atomics.waitAsync` with `Atomics.notify`. + let mut cx = Context::from_waker(&self.waker); + let (f, val) = match self.future.as_mut().poll(&mut cx) { + Poll::Ready(Ok(val)) => (&self.resolve, val), + Poll::Ready(Err(val)) => (&self.reject, val), + + // Create a `js_sys::Promise` using `Atomics.waitAsync` (or our + // polyfill) and then register its completion callback as simply + // calling this function again. + Poll::Pending => { + let promise = wait_async(&self.state.value, 0).unchecked_into::(); + let closure = Closure::once_into_js(move || { + self.poll(); + }); + promise.then(&closure); + return; + } + }; + f.call1(&JsValue::undefined(), &val).unwrap_throw(); + } +} + +impl State { + fn wake(&self) { + // Attempt to notify us by storing 1. If we're already 1 then we were + // previously notified and there's nothing to do. Otherwise we execute + // the native `notify` instruction to wake up the corresponding + // `waitAsync` that was waiting for the transition from 0 to 1. + let prev = self.value.swap(1, SeqCst); + if prev == 1 { + return; + } + debug_assert_eq!(prev, 0); + unsafe { + core::arch::wasm32::atomic_notify( + &self.value as *const AtomicI32 as *mut i32, + 1, // number of threads to notify + ); + } + } + + unsafe fn into_raw_waker(me: Arc) -> RawWaker { + const VTABLE: RawWakerVTable = + RawWakerVTable::new(raw_clone, raw_wake, raw_wake_by_ref, raw_drop); + RawWaker::new(Arc::into_raw(me) as *const (), &VTABLE) + } +} + +unsafe fn raw_clone(ptr: *const ()) -> RawWaker { + let ptr = ManuallyDrop::new(Arc::from_raw(ptr as *const State)); + State::into_raw_waker((*ptr).clone()) +} + +unsafe fn raw_wake(ptr: *const ()) { + let ptr = Arc::from_raw(ptr as *const State); + ptr.wake(); +} + +unsafe fn raw_wake_by_ref(ptr: *const ()) { + let ptr = ManuallyDrop::new(Arc::from_raw(ptr as *const State)); + ptr.wake(); +} + +unsafe fn raw_drop(ptr: *const ()) { + drop(Arc::from_raw(ptr as *const State)); +} + +fn wait_async(ptr: &AtomicI32, val: i32) -> js_sys::Promise { + // If `Atomics.waitAsync` isn't defined (as it isn't defined anywhere today) + // then we use our fallback, otherwise we use the native function. + if Atomics::get_wait_async().is_undefined() { + crate::wait_async_polyfill::wait_async(ptr, val) + } else { + let mem = wasm_bindgen::memory().unchecked_into::(); + Atomics::wait_async(&mem.buffer(), ptr as *const AtomicI32 as i32 / 4, val) + } +} diff --git a/crates/futures/src/legacy_shared.rs b/crates/futures/src/shared.rs similarity index 54% rename from crates/futures/src/legacy_shared.rs rename to crates/futures/src/shared.rs index 8b9434555cf..71fab9d3e65 100644 --- a/crates/futures/src/legacy_shared.rs +++ b/crates/futures/src/shared.rs @@ -1,10 +1,10 @@ -use futures::future; -use futures::prelude::*; -use futures::sync::oneshot; use js_sys::Promise; use std::cell::RefCell; use std::fmt; +use std::future::Future; +use std::pin::Pin; use std::rc::Rc; +use std::task::{Context, Poll, Waker}; use wasm_bindgen::prelude::*; /// A Rust `Future` backed by a JavaScript `Promise`. @@ -16,7 +16,13 @@ use wasm_bindgen::prelude::*; /// /// Currently this type is constructed with `JsFuture::from`. pub struct JsFuture { - rx: oneshot::Receiver>, + inner: Rc>, +} + +struct Inner { + result: Option>, + task: Option, + callbacks: Option<(Closure, Closure)>, } impl fmt::Debug for JsFuture { @@ -42,49 +48,54 @@ impl From for JsFuture { // have to be self-contained. Through the `Closure::once` and some // `Rc`-trickery we can arrange for both instances of `Closure`, and the // `Rc`, to all be destroyed once the first one is called. - let (tx, rx) = oneshot::channel(); - let state = Rc::new(RefCell::new(None)); + let state = Rc::new(RefCell::new(Inner { + result: None, + task: None, + callbacks: None, + })); let state2 = state.clone(); let resolve = Closure::once(move |val| finish(&state2, Ok(val))); let state2 = state.clone(); let reject = Closure::once(move |val| finish(&state2, Err(val))); js.then2(&resolve, &reject); - *state.borrow_mut() = Some((tx, resolve, reject)); + state.borrow_mut().callbacks = Some((resolve, reject)); - return JsFuture { rx }; + return JsFuture { inner: state }; - fn finish( - state: &RefCell< - Option<( - oneshot::Sender>, - Closure, - Closure, - )>, - >, - val: Result, - ) { - match state.borrow_mut().take() { - // We don't have any guarantee that anyone's still listening at this - // point (the Rust `JsFuture` could have been dropped) so simply - // ignore any errors here. - Some((tx, _, _)) => drop(tx.send(val)), - None => wasm_bindgen::throw_str("cannot finish twice"), + fn finish(state: &RefCell, val: Result) { + // First up drop our closures as they'll never be invoked again and + // this is our chance to clean up their state. Next store the value + // into the internal state, and then finally if any task was waiting + // on the value wake it up and let them know it's there. + let task = { + let mut state = state.borrow_mut(); + debug_assert!(state.callbacks.is_some()); + debug_assert!(state.result.is_none()); + drop(state.callbacks.take()); + state.result = Some(val); + state.task.take() + }; + if let Some(task) = task { + task.wake() } } } } impl Future for JsFuture { - type Item = JsValue; - type Error = JsValue; + type Output = Result; - fn poll(&mut self) -> Poll { - match self.rx.poll() { - Ok(Async::Ready(val)) => val.map(Async::Ready), - Ok(Async::NotReady) => Ok(Async::NotReady), - Err(_) => wasm_bindgen::throw_str("cannot cancel"), + fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll { + let mut inner = self.inner.borrow_mut(); + // If our value has come in then we return it... + if let Some(val) = inner.result.take() { + return Poll::Ready(val) } + // ... otherwise we arrange ourselves to get woken up once the value + // does come in + inner.task = Some(cx.waker().clone()); + Poll::Pending } } @@ -98,11 +109,10 @@ impl Future for JsFuture { /// This function has the same panic behavior as `future_to_promise`. pub fn spawn_local(future: F) where - F: Future + 'static, + F: Future + 'static, { - crate::future_to_promise( - future - .map(|()| JsValue::undefined()) - .or_else(|()| future::ok::(JsValue::undefined())), - ); + crate::future_to_promise(async { + future.await; + Ok(JsValue::undefined()) + }); } diff --git a/crates/futures/src/singlethread.rs b/crates/futures/src/singlethread.rs new file mode 100644 index 00000000000..dae2cfa438a --- /dev/null +++ b/crates/futures/src/singlethread.rs @@ -0,0 +1,142 @@ +pub use crate::shared::{spawn_local, JsFuture}; +use js_sys::Promise; +use std::cell::{Cell, RefCell}; +use std::future::Future; +use std::mem::ManuallyDrop; +use std::pin::Pin; +use std::rc::Rc; +use std::task::{Context, Poll, RawWaker, RawWakerVTable, Waker}; +use wasm_bindgen::prelude::*; +use wasm_bindgen::JsCast; + +/// Converts a Rust `Future` into a JavaScript `Promise`. +/// +/// This function will take any future in Rust and schedule it to be executed, +/// returning a JavaScript `Promise` which can then be passed back to JavaScript +/// to get plumbed into the rest of a system. +/// +/// The `future` provided must adhere to `'static` because it'll be scheduled +/// to run in the background and cannot contain any stack references. The +/// returned `Promise` will be resolved or rejected when the future completes, +/// depending on whether it finishes with `Ok` or `Err`. +/// +/// # Panics +/// +/// Note that in wasm panics are currently translated to aborts, but "abort" in +/// this case means that a JavaScript exception is thrown. The wasm module is +/// still usable (likely erroneously) after Rust panics. +/// +/// If the `future` provided panics then the returned `Promise` **will not +/// resolve**. Instead it will be a leaked promise. This is an unfortunate +/// limitation of wasm currently that's hoped to be fixed one day! +pub fn future_to_promise(future: F) -> Promise +where + F: Future> + 'static, +{ + _future_to_promise(Box::new(future)) +} + +fn _future_to_promise(future: Box>>) -> Promise { + let mut future = Some(future); + js_sys::Promise::new(&mut move |resolve, reject| { + let future = match future.take() { + Some(f) => f, + None => wasm_bindgen::throw_str("cannot invoke twice"), + }; + let state = Rc::new(State { + queued: Cell::new(true), + future: RefCell::new(Some(Pin::from(future))), + resolve, + reject, + }); + State::poll(&state); + }) +} + +struct State { + queued: Cell, + future: RefCell>>>>>, + resolve: js_sys::Function, + reject: js_sys::Function, +} + +impl State { + fn poll(me: &Rc) { + debug_assert!(me.queued.get()); + me.queued.set(false); + let waker = unsafe { Waker::from_raw(State::into_raw_waker(me.clone())) }; + let mut future = me.future.borrow_mut(); + + let mut done = false; + if let Some(future) = &mut *future { + match Future::poll(future.as_mut(), &mut Context::from_waker(&waker)) { + Poll::Ready(Ok(val)) => { + me.resolve.call1(&JsValue::undefined(), &val).unwrap_throw(); + done = true; + } + Poll::Ready(Err(val)) => { + me.reject.call1(&JsValue::undefined(), &val).unwrap_throw(); + done = true; + } + Poll::Pending => {} + } + } + if done { + debug_assert!(future.is_some()); + drop(future.take()); + return; + } + } + + fn wake(me: &Rc) { + // If we're already enqueued on the microtask queue there's nothing else + // to do, this is a duplicate notification. + if me.queued.replace(true) { + return; + } + + // Use `Promise.then` on a resolved promise to place our execution + // onto the next turn of the microtask queue, enqueueing our poll + // operation. If we were to poll immediately we run the risk of blowing + // the stack. + let promise = Promise::resolve(&JsValue::undefined()); + let promise = promise.unchecked_ref::(); + let me = me.clone(); + let closure = Closure::once_into_js(move || { + State::poll(&me); + }); + promise.then(&closure); + + #[wasm_bindgen] + extern "C" { + type MyPromise; + #[wasm_bindgen(js_class = Promise, method)] + fn then(this: &MyPromise, closure: &JsValue); + } + } + + unsafe fn into_raw_waker(me: Rc) -> RawWaker { + const VTABLE: RawWakerVTable = + RawWakerVTable::new(raw_clone, raw_wake, raw_wake_by_ref, raw_drop); + RawWaker::new(Rc::into_raw(me) as *const (), &VTABLE) + } +} + +unsafe fn raw_clone(ptr: *const ()) -> RawWaker { + let ptr = ManuallyDrop::new(Rc::from_raw(ptr as *const State)); + State::into_raw_waker((*ptr).clone()) +} + +unsafe fn raw_wake(ptr: *const ()) { + let ptr = Rc::from_raw(ptr as *const State); + State::wake(&ptr); +} + +unsafe fn raw_wake_by_ref(ptr: *const ()) { + let ptr = ManuallyDrop::new(Rc::from_raw(ptr as *const State)); + State::wake(&ptr); +} + +unsafe fn raw_drop(ptr: *const ()) { + drop(Rc::from_raw(ptr as *const State)); +} diff --git a/crates/js-sys/Cargo.toml b/crates/js-sys/Cargo.toml index becb21069ec..a8735e42632 100644 --- a/crates/js-sys/Cargo.toml +++ b/crates/js-sys/Cargo.toml @@ -22,6 +22,5 @@ doctest = false wasm-bindgen = { path = "../..", version = "0.2.50" } [target.'cfg(target_arch = "wasm32")'.dev-dependencies] -futures = "0.1.20" -wasm-bindgen-test = { path = '../test', version = '=0.2.50' } -wasm-bindgen-futures = { path = '../futures', version = '=0.3.27' } +wasm-bindgen-test = { path = '../test', version = '=0.3.0' } +wasm-bindgen-futures = { path = '../futures', version = '0.4.0' } diff --git a/crates/js-sys/tests/wasm/WebAssembly.rs b/crates/js-sys/tests/wasm/WebAssembly.rs index a662f9f0e2d..7a06e256a2c 100644 --- a/crates/js-sys/tests/wasm/WebAssembly.rs +++ b/crates/js-sys/tests/wasm/WebAssembly.rs @@ -1,4 +1,3 @@ -use futures::Future; use js_sys::*; use wasm_bindgen::{prelude::*, JsCast}; use wasm_bindgen_futures::JsFuture; @@ -38,32 +37,25 @@ fn validate() { assert!(WebAssembly::validate(&get_bad_type_wasm()).is_err()); } -#[wasm_bindgen_test(async)] -fn compile_compile_error() -> impl Future { +#[wasm_bindgen_test] +async fn compile_compile_error() { let p = WebAssembly::compile(&get_invalid_wasm()); - JsFuture::from(p).map(|_| unreachable!()).or_else(|e| { - assert!(e.is_instance_of::()); - Ok(()) - }) + let e = JsFuture::from(p).await.unwrap_err(); + assert!(e.is_instance_of::()); } -#[wasm_bindgen_test(async)] -fn compile_type_error() -> impl Future { +#[wasm_bindgen_test] +async fn compile_type_error() { let p = WebAssembly::compile(&get_bad_type_wasm()); - JsFuture::from(p).map(|_| unreachable!()).or_else(|e| { - assert!(e.is_instance_of::()); - Ok(()) - }) + let e = JsFuture::from(p).await.unwrap_err(); + assert!(e.is_instance_of::()); } -#[wasm_bindgen_test(async)] -fn compile_valid() -> impl Future { +#[wasm_bindgen_test] +async fn compile_valid() { let p = WebAssembly::compile(&get_valid_wasm()); - JsFuture::from(p) - .map(|module| { - assert!(module.is_instance_of::()); - }) - .map_err(|_| unreachable!()) + let module = JsFuture::from(p).await.unwrap(); + assert!(module.is_instance_of::()); } #[wasm_bindgen_test] @@ -182,26 +174,24 @@ fn webassembly_instance() { assert!(Reflect::has(exports.as_ref(), &"exported_func".into()).unwrap()); } -#[wasm_bindgen_test(async)] -fn instantiate_module() -> impl Future { +#[wasm_bindgen_test] +async fn instantiate_module() { let module = WebAssembly::Module::new(&get_valid_wasm()).unwrap(); let imports = get_imports(); let p = WebAssembly::instantiate_module(&module, &imports); - JsFuture::from(p).map(|inst| { - assert!(inst.is_instance_of::()); - }) + let inst = JsFuture::from(p).await.unwrap(); + assert!(inst.is_instance_of::()); } -#[wasm_bindgen_test(async)] -fn instantiate_streaming() -> impl Future { +#[wasm_bindgen_test] +async fn instantiate_streaming() { let response = Promise::resolve(&get_valid_wasm()); let imports = get_imports(); let p = WebAssembly::instantiate_streaming(&response, &imports); - JsFuture::from(p).map(|obj| { - assert!(Reflect::get(obj.as_ref(), &"instance".into()) - .unwrap() - .is_instance_of::()); - }) + let obj = JsFuture::from(p).await.unwrap(); + assert!(Reflect::get(obj.as_ref(), &"instance".into()) + .unwrap() + .is_instance_of::()); } #[wasm_bindgen_test] diff --git a/crates/js-sys/tests/wasm/main.rs b/crates/js-sys/tests/wasm/main.rs index abc86acd576..17f80bdfc99 100755 --- a/crates/js-sys/tests/wasm/main.rs +++ b/crates/js-sys/tests/wasm/main.rs @@ -1,12 +1,6 @@ #![cfg(target_arch = "wasm32")] #![allow(non_snake_case)] -extern crate futures; -extern crate js_sys; -extern crate wasm_bindgen; -extern crate wasm_bindgen_futures; -extern crate wasm_bindgen_test; - pub mod Array; pub mod ArrayBuffer; pub mod ArrayIterator; diff --git a/crates/test-macro/Cargo.toml b/crates/test-macro/Cargo.toml index 64071e4640b..e3e6f01d014 100644 --- a/crates/test-macro/Cargo.toml +++ b/crates/test-macro/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "wasm-bindgen-test-macro" -version = "0.2.50" +version = "0.3.0" authors = ["The wasm-bindgen Developers"] description = "Internal testing macro for wasm-bindgen" license = "MIT/Apache-2.0" @@ -8,8 +8,8 @@ repository = "https://github.com/rustwasm/wasm-bindgen" edition = "2018" [dependencies] -proc-macro2 = "0.4" -quote = "0.6" +proc-macro2 = "1.0" +quote = "1.0" [lib] proc-macro = true diff --git a/crates/test-macro/src/lib.rs b/crates/test-macro/src/lib.rs index 65ac76cb181..ce7c5851655 100644 --- a/crates/test-macro/src/lib.rs +++ b/crates/test-macro/src/lib.rs @@ -35,6 +35,9 @@ pub fn wasm_bindgen_test( while let Some(token) = body.next() { leading_tokens.push(token.clone()); if let TokenTree::Ident(token) = token { + if token == "async" { + r#async = true; + } if token == "fn" { break; } diff --git a/crates/test/Cargo.toml b/crates/test/Cargo.toml index 700d61b3002..f8d560088fd 100644 --- a/crates/test/Cargo.toml +++ b/crates/test/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "wasm-bindgen-test" -version = "0.2.50" +version = "0.3.0" authors = ["The wasm-bindgen Developers"] description = "Internal testing crate for wasm-bindgen" license = "MIT/Apache-2.0" @@ -9,12 +9,11 @@ edition = "2018" [dependencies] console_error_panic_hook = '0.1' -futures = "0.1" js-sys = { path = '../js-sys', version = '0.3.27' } scoped-tls = "1.0" wasm-bindgen = { path = '../..', version = '0.2.50' } -wasm-bindgen-futures = { path = '../futures', version = '0.3.27' } -wasm-bindgen-test-macro = { path = '../test-macro', version = '=0.2.50' } +wasm-bindgen-futures = { path = '../futures', version = '0.4.0' } +wasm-bindgen-test-macro = { path = '../test-macro', version = '=0.3.0' } [lib] test = false diff --git a/crates/test/sample/Cargo.toml b/crates/test/sample/Cargo.toml index de4f2756026..05544ab4a3d 100644 --- a/crates/test/sample/Cargo.toml +++ b/crates/test/sample/Cargo.toml @@ -2,12 +2,12 @@ name = "sample" version = "0.1.0" authors = ["The wasm-bindgen Developers"] +edition = "2018" [lib] test = false [dependencies] -futures = "0.1" js-sys = { path = '../../js-sys' } wasm-bindgen = { path = '../../..' } wasm-bindgen-futures = { path = '../../futures' } diff --git a/crates/test/sample/src/lib.rs b/crates/test/sample/src/lib.rs index 23852f99a94..5c878ccf63c 100644 --- a/crates/test/sample/src/lib.rs +++ b/crates/test/sample/src/lib.rs @@ -1,28 +1,23 @@ -#[macro_use] -extern crate futures; -extern crate js_sys; -extern crate wasm_bindgen; -extern crate wasm_bindgen_futures; - -use std::time::Duration; - -use futures::prelude::*; use js_sys::Promise; +use std::task::{Poll, Context}; +use std::pin::Pin; +use std::time::Duration; use wasm_bindgen::prelude::*; +use std::future::Future; use wasm_bindgen_futures::JsFuture; pub struct Timeout { - id: u32, + id: JsValue, inner: JsFuture, } #[wasm_bindgen] extern "C" { #[wasm_bindgen(js_name = setTimeout)] - fn set_timeout(closure: JsValue, millis: f64) -> u32; + fn set_timeout(closure: JsValue, millis: f64) -> JsValue; #[wasm_bindgen(js_name = clearTimeout)] - fn clear_timeout(id: u32); + fn clear_timeout(id: &JsValue); } impl Timeout { @@ -47,17 +42,15 @@ impl Timeout { } impl Future for Timeout { - type Item = (); - type Error = JsValue; + type Output = (); - fn poll(&mut self) -> Poll<(), JsValue> { - let _obj = try_ready!(self.inner.poll()); - Ok(().into()) + fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<()> { + Pin::new(&mut self.inner).poll(cx).map(|_| ()) } } impl Drop for Timeout { fn drop(&mut self) { - clear_timeout(self.id); + clear_timeout(&self.id); } } diff --git a/crates/test/sample/tests/browser.rs b/crates/test/sample/tests/browser.rs index 0f2bcd4c807..39c142bcf07 100644 --- a/crates/test/sample/tests/browser.rs +++ b/crates/test/sample/tests/browser.rs @@ -1,8 +1,3 @@ -extern crate futures; -extern crate sample; -extern crate wasm_bindgen; -extern crate wasm_bindgen_test; - wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser); pub mod common; diff --git a/crates/test/sample/tests/common/mod.rs b/crates/test/sample/tests/common/mod.rs index cde7d6deb7f..82860db4d50 100644 --- a/crates/test/sample/tests/common/mod.rs +++ b/crates/test/sample/tests/common/mod.rs @@ -1,8 +1,5 @@ use std::time::Duration; - -use futures::prelude::*; use sample::Timeout; -use wasm_bindgen::prelude::*; use wasm_bindgen_test::*; #[wasm_bindgen_test] @@ -10,15 +7,13 @@ fn pass() { console_log!("DO NOT SEE ME"); } -#[wasm_bindgen_test(async)] -fn pass_after_2s() -> impl Future { +#[wasm_bindgen_test] +async fn pass_after_2s() { console_log!("immediate log"); - Timeout::new(Duration::new(1, 0)).and_then(|()| { - console_log!("log after 1s"); - Timeout::new(Duration::new(1, 0)).map(|()| { - console_log!("log at end"); - }) - }) + Timeout::new(Duration::new(1, 0)).await; + console_log!("log after 1s"); + Timeout::new(Duration::new(1, 0)).await; + console_log!("log at end"); } #[wasm_bindgen_test] @@ -27,16 +22,13 @@ fn fail() { panic!("this is a failing test"); } -#[wasm_bindgen_test(async)] -fn fail_after_3s() -> impl Future { +#[wasm_bindgen_test] +async fn fail_after_3s() { console_log!("immediate log"); - Timeout::new(Duration::new(1, 0)).and_then(|()| { - console_log!("log after 1s"); - Timeout::new(Duration::new(1, 0)).and_then(|()| { - console_log!("log after 2s"); - Timeout::new(Duration::new(1, 0)).map(|()| { - panic!("end"); - }) - }) - }) + Timeout::new(Duration::new(1, 0)).await; + console_log!("log after 1s"); + Timeout::new(Duration::new(1, 0)).await; + console_log!("log after 2s"); + Timeout::new(Duration::new(1, 0)).await; + panic!("end"); } diff --git a/crates/test/sample/tests/node.rs b/crates/test/sample/tests/node.rs index e9cb596c3fa..34994bf5afc 100644 --- a/crates/test/sample/tests/node.rs +++ b/crates/test/sample/tests/node.rs @@ -1,6 +1 @@ -extern crate futures; -extern crate sample; -extern crate wasm_bindgen; -extern crate wasm_bindgen_test; - pub mod common; diff --git a/crates/test/src/rt/mod.rs b/crates/test/src/rt/mod.rs index 597c95c8734..bf08fe19e42 100644 --- a/crates/test/src/rt/mod.rs +++ b/crates/test/src/rt/mod.rs @@ -87,14 +87,14 @@ // Overall this is all somewhat in flux as it's pretty new, and feedback is // always of course welcome! +use console_error_panic_hook; +use js_sys::{Array, Function, Promise}; use std::cell::{Cell, RefCell}; use std::fmt; +use std::future::Future; +use std::pin::Pin; use std::rc::Rc; - -use console_error_panic_hook; -use futures::future; -use futures::prelude::*; -use js_sys::{Array, Function, Promise}; +use std::task::{self, Poll}; use wasm_bindgen::prelude::*; use wasm_bindgen_futures::future_to_promise; @@ -157,7 +157,7 @@ struct State { /// future is polled. struct Test { name: String, - future: Box>, + future: Pin>>>, output: Rc>, } @@ -286,10 +286,11 @@ impl Context { // Now that we've collected all our tests we wrap everything up in a // future to actually do all the processing, and pass it out to JS as a // `Promise`. - let future = ExecuteTests(self.state.clone()) - .map(JsValue::from) - .map_err(|e| match e {}); - future_to_promise(future) + let state = self.state.clone(); + future_to_promise(async { + let passed = ExecuteTests(state).await; + Ok(JsValue::from(passed)) + }) } } @@ -358,7 +359,7 @@ impl Context { /// Entry point for a synchronous test in wasm. The `#[wasm_bindgen_test]` /// macro generates invocations of this method. pub fn execute_sync(&self, name: &str, f: impl FnOnce() + 'static) { - self.execute(name, future::lazy(|| Ok(f()))); + self.execute(name, async { f() }); } /// Entry point for an asynchronous in wasm. The @@ -366,12 +367,12 @@ impl Context { /// method. pub fn execute_async(&self, name: &str, f: impl FnOnce() -> F + 'static) where - F: Future + 'static, + F: Future + 'static, { - self.execute(name, future::lazy(f)) + self.execute(name, async { f().await }) } - fn execute(&self, name: &str, test: impl Future + 'static) { + fn execute(&self, name: &str, test: impl Future + 'static) { // If our test is filtered out, record that it was filtered and move // on, nothing to do here. let filter = self.state.filter.borrow(); @@ -392,7 +393,7 @@ impl Context { }; self.state.remaining.borrow_mut().push(Test { name: name.to_string(), - future: Box::new(future), + future: Pin::from(Box::new(future)), output, }); } @@ -400,23 +401,19 @@ impl Context { struct ExecuteTests(Rc); -enum Never {} - impl Future for ExecuteTests { - type Item = bool; - type Error = Never; + type Output = bool; - fn poll(&mut self) -> Poll { + fn poll(self: Pin<&mut Self>, cx: &mut task::Context) -> Poll { let mut running = self.0.running.borrow_mut(); let mut remaining = self.0.remaining.borrow_mut(); // First up, try to make progress on all active tests. Remove any // finished tests. for i in (0..running.len()).rev() { - let result = match running[i].future.poll() { - Ok(Async::Ready(_jsavl)) => Ok(()), - Ok(Async::NotReady) => continue, - Err(e) => Err(e), + let result = match running[i].future.as_mut().poll(cx) { + Poll::Ready(result) => result, + Poll::Pending => continue, }; let test = running.remove(i); self.0.log_test_result(test, result); @@ -431,13 +428,12 @@ impl Future for ExecuteTests { Some(test) => test, None => break, }; - let result = match test.future.poll() { - Ok(Async::Ready(())) => Ok(()), - Ok(Async::NotReady) => { + let result = match test.future.as_mut().poll(cx) { + Poll::Ready(result) => result, + Poll::Pending => { running.push(test); continue; } - Err(e) => Err(e), }; self.0.log_test_result(test, result); } @@ -445,7 +441,7 @@ impl Future for ExecuteTests { // Tests are still executing, we're registered to get a notification, // keep going. if running.len() != 0 { - return Ok(Async::NotReady); + return Poll::Pending; } // If there are no tests running then we must have finished everything, @@ -454,7 +450,7 @@ impl Future for ExecuteTests { self.0.print_results(); let all_passed = self.0.failures.borrow().len() == 0; - Ok(Async::Ready(all_passed)) + Poll::Ready(all_passed) } } @@ -561,17 +557,28 @@ extern "C" { fn __wbg_test_invoke(f: &mut dyn FnMut()) -> Result<(), JsValue>; } -impl> Future for TestFuture { - type Item = F::Item; - type Error = F::Error; +impl Future for TestFuture { + type Output = Result; - fn poll(&mut self) -> Poll { - let test = &mut self.test; + fn poll(self: Pin<&mut Self>, cx: &mut task::Context) -> Poll { + let output = self.output.clone(); + // Use `new_unchecked` here to project our own pin, and we never + // move `test` so this should be safe + let test = unsafe { Pin::map_unchecked_mut(self, |me| &mut me.test) }; let mut future_output = None; - CURRENT_OUTPUT.set(&self.output, || { - __wbg_test_invoke(&mut || future_output = Some(test.poll())) - })?; - future_output.unwrap() + let result = CURRENT_OUTPUT.set(&output, || { + let mut test = Some(test); + __wbg_test_invoke(&mut || { + let test = test.take().unwrap_throw(); + future_output = Some(test.poll(cx)) + }) + }); + match (result, future_output) { + (_, Some(Poll::Ready(e))) => Poll::Ready(Ok(e)), + (_, Some(Poll::Pending)) => Poll::Pending, + (Err(e), _) => Poll::Ready(Err(e)), + (Ok(_), None) => wasm_bindgen::throw_str("invalid poll state"), + } } } diff --git a/crates/web-sys/Cargo.toml b/crates/web-sys/Cargo.toml index 122dde4c97e..1301d20abaa 100644 --- a/crates/web-sys/Cargo.toml +++ b/crates/web-sys/Cargo.toml @@ -30,9 +30,8 @@ wasm-bindgen = { path = "../..", version = "0.2.50" } js-sys = { path = '../js-sys', version = '0.3.27' } [target.'cfg(target_arch = "wasm32")'.dev-dependencies] -futures = "0.1" -wasm-bindgen-test = { path = '../test', version = '0.2.50' } -wasm-bindgen-futures = { path = '../futures', version = '0.3.27' } +wasm-bindgen-test = { path = '../test', version = '0.3.0' } +wasm-bindgen-futures = { path = '../futures', version = '0.4.0' } # This list is generated by passing `__WASM_BINDGEN_DUMP_FEATURES=foo` when # compiling this crate which dumps the total list of features to a file called diff --git a/crates/web-sys/tests/wasm/event.rs b/crates/web-sys/tests/wasm/event.rs index bd12cb0dda8..08d74ec83ad 100644 --- a/crates/web-sys/tests/wasm/event.rs +++ b/crates/web-sys/tests/wasm/event.rs @@ -1,4 +1,3 @@ -use futures::future::Future; use js_sys::{Object, Promise}; use wasm_bindgen::prelude::*; use wasm_bindgen::JsCast; @@ -11,22 +10,22 @@ extern "C" { fn new_event() -> Promise; } -#[wasm_bindgen_test(async)] -fn event() -> impl Future { - JsFuture::from(new_event()).map(Event::from).map(|event| { - // All DOM interfaces should inherit from `Object`. - assert!(event.is_instance_of::()); - let _: &Object = event.as_ref(); +#[wasm_bindgen_test] +async fn event() { + let result = JsFuture::from(new_event()).await.unwrap(); + let event = Event::from(result); + // All DOM interfaces should inherit from `Object`. + assert!(event.is_instance_of::()); + let _: &Object = event.as_ref(); - // These should match `new Event`. - assert!(event.bubbles()); - assert!(event.cancelable()); - assert!(event.composed()); + // These should match `new Event`. + assert!(event.bubbles()); + assert!(event.cancelable()); + assert!(event.composed()); - // The default behavior not initially prevented, but after - // we call `prevent_default` it better be. - assert!(!event.default_prevented()); - event.prevent_default(); - assert!(event.default_prevented()); - }) + // The default behavior not initially prevented, but after + // we call `prevent_default` it better be. + assert!(!event.default_prevented()); + event.prevent_default(); + assert!(event.default_prevented()); } diff --git a/crates/web-sys/tests/wasm/main.rs b/crates/web-sys/tests/wasm/main.rs index f27d6af71f5..3d0c125a381 100644 --- a/crates/web-sys/tests/wasm/main.rs +++ b/crates/web-sys/tests/wasm/main.rs @@ -1,12 +1,5 @@ #![cfg(target_arch = "wasm32")] -extern crate futures; -extern crate js_sys; -extern crate wasm_bindgen; -extern crate wasm_bindgen_futures; -extern crate wasm_bindgen_test; -extern crate web_sys; - use wasm_bindgen_test::*; wasm_bindgen_test_configure!(run_in_browser); diff --git a/crates/web-sys/tests/wasm/response.rs b/crates/web-sys/tests/wasm/response.rs index f22495abd6b..045062df14f 100644 --- a/crates/web-sys/tests/wasm/response.rs +++ b/crates/web-sys/tests/wasm/response.rs @@ -1,8 +1,3 @@ -extern crate futures; -extern crate js_sys; -extern crate wasm_bindgen_futures; - -use futures::Future; use js_sys::{ArrayBuffer, DataView}; use wasm_bindgen::prelude::*; use wasm_bindgen::JsCast; @@ -23,20 +18,19 @@ fn test_response_from_js() { assert_eq!(response.status(), 501); } -#[wasm_bindgen_test(async)] -fn test_response_from_bytes() -> impl Future { +#[wasm_bindgen_test] +async fn test_response_from_bytes() { let mut bytes: [u8; 3] = [1, 3, 5]; let response = Response::new_with_opt_u8_array(Some(&mut bytes)).unwrap(); assert!(response.ok()); assert_eq!(response.status(), 200); let buf_promise = response.array_buffer().unwrap(); - JsFuture::from(buf_promise).map(move |buf_val| { - assert!(buf_val.is_instance_of::()); - let array_buf: ArrayBuffer = buf_val.dyn_into().unwrap(); - let data_view = DataView::new(&array_buf, 0, bytes.len()); - for (i, byte) in bytes.iter().enumerate() { - assert_eq!(&data_view.get_uint8(i), byte); - } - }) + let buf_val = JsFuture::from(buf_promise).await.unwrap(); + assert!(buf_val.is_instance_of::()); + let array_buf: ArrayBuffer = buf_val.dyn_into().unwrap(); + let data_view = DataView::new(&array_buf, 0, bytes.len()); + for (i, byte) in bytes.iter().enumerate() { + assert_eq!(&data_view.get_uint8(i), byte); + } } diff --git a/crates/web-sys/tests/wasm/rtc_rtp_transceiver_direction.rs b/crates/web-sys/tests/wasm/rtc_rtp_transceiver_direction.rs index 15dbe7aa11a..b21edcb6c59 100644 --- a/crates/web-sys/tests/wasm/rtc_rtp_transceiver_direction.rs +++ b/crates/web-sys/tests/wasm/rtc_rtp_transceiver_direction.rs @@ -2,11 +2,6 @@ use wasm_bindgen::{prelude::*, JsCast}; use wasm_bindgen_futures::JsFuture; use wasm_bindgen_test::*; -use futures::{ - future::{ok, IntoFuture}, - Future, -}; - use web_sys::{ RtcPeerConnection, RtcRtpTransceiver, RtcRtpTransceiverDirection, RtcRtpTransceiverInit, RtcSessionDescriptionInit, @@ -20,10 +15,10 @@ extern "C" { fn is_unified_avail() -> bool; } -#[wasm_bindgen_test(async)] -fn rtc_rtp_transceiver_direction() -> Box> { +#[wasm_bindgen_test] +async fn rtc_rtp_transceiver_direction() { if !is_unified_avail() { - return Box::new(Ok(()).into_future()); + return; } let mut tr_init: RtcRtpTransceiverInit = RtcRtpTransceiverInit::new(); @@ -39,54 +34,39 @@ fn rtc_rtp_transceiver_direction() -> Box let pc2: RtcPeerConnection = RtcPeerConnection::new().unwrap(); - let r = exchange_sdps(pc1, pc2).and_then(move |(_, p2)| { - assert_eq!(tr1.direction(), RtcRtpTransceiverDirection::Sendonly); - assert_eq!( - tr1.current_direction(), - Some(RtcRtpTransceiverDirection::Sendonly) - ); - - let tr2: RtcRtpTransceiver = js_sys::try_iter(&p2.get_transceivers()) - .unwrap() - .unwrap() - .next() - .unwrap() - .unwrap() - .unchecked_into(); - - assert_eq!(tr2.direction(), RtcRtpTransceiverDirection::Recvonly); - assert_eq!( - tr2.current_direction(), - Some(RtcRtpTransceiverDirection::Recvonly) - ); + let (_, p2) = exchange_sdps(pc1, pc2).await; + assert_eq!(tr1.direction(), RtcRtpTransceiverDirection::Sendonly); + assert_eq!( + tr1.current_direction(), + Some(RtcRtpTransceiverDirection::Sendonly) + ); - Ok(()) - }); + let tr2: RtcRtpTransceiver = js_sys::try_iter(&p2.get_transceivers()) + .unwrap() + .unwrap() + .next() + .unwrap() + .unwrap() + .unchecked_into(); - Box::new(r) + assert_eq!(tr2.direction(), RtcRtpTransceiverDirection::Recvonly); + assert_eq!( + tr2.current_direction(), + Some(RtcRtpTransceiverDirection::Recvonly) + ); } -fn exchange_sdps( +async fn exchange_sdps( p1: RtcPeerConnection, p2: RtcPeerConnection, -) -> impl Future { - JsFuture::from(p1.create_offer()) - .and_then(move |offer| { - let offer = offer.unchecked_into::(); - JsFuture::from(p1.set_local_description(&offer)).join4( - JsFuture::from(p2.set_remote_description(&offer)), - Ok(p1), - Ok(p2), - ) - }) - .and_then(|(_, _, p1, p2)| JsFuture::from(p2.create_answer()).join3(Ok(p1), Ok(p2))) - .and_then(|(answer, p1, p2)| { - let answer = answer.unchecked_into::(); - JsFuture::from(p2.set_local_description(&answer)).join4( - JsFuture::from(p1.set_remote_description(&answer)), - Ok(p1), - Ok(p2), - ) - }) - .and_then(|(_, _, p1, p2)| Ok((p1, p2))) +) -> (RtcPeerConnection, RtcPeerConnection) { + let offer = JsFuture::from(p1.create_offer()).await.unwrap(); + let offer = offer.unchecked_into::(); + JsFuture::from(p1.set_local_description(&offer)).await.unwrap(); + JsFuture::from(p2.set_remote_description(&offer)).await.unwrap(); + let answer = JsFuture::from(p2.create_answer()).await.unwrap(); + let answer = answer.unchecked_into::(); + JsFuture::from(p2.set_local_description(&answer)).await.unwrap(); + JsFuture::from(p1.set_remote_description(&answer)).await.unwrap(); + (p1, p2) } diff --git a/crates/web-sys/tests/wasm/whitelisted_immutable_slices.rs b/crates/web-sys/tests/wasm/whitelisted_immutable_slices.rs index e58987df3de..92c3161904d 100644 --- a/crates/web-sys/tests/wasm/whitelisted_immutable_slices.rs +++ b/crates/web-sys/tests/wasm/whitelisted_immutable_slices.rs @@ -11,7 +11,6 @@ //! @see https://github.com/rustwasm/wasm-bindgen/issues/1005 use wasm_bindgen::prelude::*; -use wasm_bindgen_test::*; use web_sys::{WebGl2RenderingContext, WebGlRenderingContext}; #[wasm_bindgen(module = "/tests/wasm/element.js")] diff --git a/examples/fetch/Cargo.toml b/examples/fetch/Cargo.toml index 4e59373c143..a76b418f8fe 100644 --- a/examples/fetch/Cargo.toml +++ b/examples/fetch/Cargo.toml @@ -8,10 +8,9 @@ edition = "2018" crate-type = ["cdylib"] [dependencies] -futures = "0.1.20" wasm-bindgen = { version = "0.2.50", features = ["serde-serialize"] } js-sys = "0.3.27" -wasm-bindgen-futures = "0.3.27" +wasm-bindgen-futures = "0.4.0" serde = { version = "1.0.80", features = ["derive"] } serde_derive = "^1.0.59" diff --git a/examples/fetch/src/lib.rs b/examples/fetch/src/lib.rs index f24627b70a1..e65bdf37b55 100644 --- a/examples/fetch/src/lib.rs +++ b/examples/fetch/src/lib.rs @@ -1,4 +1,3 @@ -use futures::{future, Future}; use js_sys::Promise; use serde::{Deserialize, Serialize}; use wasm_bindgen::prelude::*; @@ -37,6 +36,10 @@ pub struct Signature { #[wasm_bindgen] pub fn run() -> Promise { + future_to_promise(run_()) +} + +async fn run_() -> Result { let mut opts = RequestInit::new(); opts.method("GET"); opts.mode(RequestMode::Cors); @@ -44,36 +47,25 @@ pub fn run() -> Promise { let request = Request::new_with_str_and_init( "https://api.github.com/repos/rustwasm/wasm-bindgen/branches/master", &opts, - ) - .unwrap(); + )?; request .headers() - .set("Accept", "application/vnd.github.v3+json") - .unwrap(); + .set("Accept", "application/vnd.github.v3+json")?; let window = web_sys::window().unwrap(); - let request_promise = window.fetch_with_request(&request); + let resp_value = JsFuture::from(window.fetch_with_request(&request)).await?; + + // `resp_value` is a `Response` object. + assert!(resp_value.is_instance_of::()); + let resp: Response = resp_value.dyn_into().unwrap(); - let future = JsFuture::from(request_promise) - .and_then(|resp_value| { - // `resp_value` is a `Response` object. - assert!(resp_value.is_instance_of::()); - let resp: Response = resp_value.dyn_into().unwrap(); - resp.json() - }) - .and_then(|json_value: Promise| { - // Convert this other `Promise` into a rust `Future`. - JsFuture::from(json_value) - }) - .and_then(|json| { - // Use serde to parse the JSON into a struct. - let branch_info: Branch = json.into_serde().unwrap(); + // Convert this other `Promise` into a rust `Future`. + let json = JsFuture::from(resp.json()?).await?; - // Send the `Branch` struct back to JS as an `Object`. - future::ok(JsValue::from_serde(&branch_info).unwrap()) - }); + // Use serde to parse the JSON into a struct. + let branch_info: Branch = json.into_serde().unwrap(); - // Convert this Rust `Future` back into a JS `Promise`. - future_to_promise(future) + // Send the `Branch` struct back to JS as an `Object`. + Ok(JsValue::from_serde(&branch_info).unwrap()) } diff --git a/examples/raytrace-parallel/Cargo.toml b/examples/raytrace-parallel/Cargo.toml index 3f7ed6a258a..a36129892e7 100644 --- a/examples/raytrace-parallel/Cargo.toml +++ b/examples/raytrace-parallel/Cargo.toml @@ -9,13 +9,13 @@ crate-type = ["cdylib"] [dependencies] console_error_panic_hook = "0.1" -futures = "0.1" js-sys = "0.3.27" rayon = "1.1.0" rayon-core = "1.5.0" raytracer = { git = 'https://github.com/alexcrichton/raytracer', branch = 'update-deps' } +futures-channel-preview = "0.3.0-alpha.18" wasm-bindgen = { version = "0.2.50", features = ['serde-serialize'] } -wasm-bindgen-futures = "0.3.27" +wasm-bindgen-futures = "0.4.0" [dependencies.web-sys] version = "0.3.23" diff --git a/examples/raytrace-parallel/src/lib.rs b/examples/raytrace-parallel/src/lib.rs index 413dbed5cc3..fbb9ad68374 100644 --- a/examples/raytrace-parallel/src/lib.rs +++ b/examples/raytrace-parallel/src/lib.rs @@ -1,5 +1,4 @@ -use futures::sync::oneshot; -use futures::Future; +use futures_channel::oneshot; use js_sys::{Promise, Uint8ClampedArray, WebAssembly}; use rayon::prelude::*; use wasm_bindgen::prelude::*; @@ -90,9 +89,13 @@ impl Scene { }); drop(tx.send(rgb_data)); })?; - let done = rx - .map(move |_data| image_data(base, len, width, height).into()) - .map_err(|_| JsValue::undefined()); + + let done = async move { + match rx.await { + Ok(_data) => Ok(image_data(base, len, width, height).into()), + Err(_) => Err(JsValue::undefined()), + } + }; Ok(RenderingScene { promise: wasm_bindgen_futures::future_to_promise(done),