mirror of
https://github.com/rustwasm/wasm-bindgen.git
synced 2024-12-26 11:34:22 +03:00
Add examples/documentation for closures
This commit is contained in:
parent
176060cc8a
commit
a3e5485b86
@ -32,6 +32,7 @@ members = [
|
|||||||
"examples/math",
|
"examples/math",
|
||||||
"examples/performance",
|
"examples/performance",
|
||||||
"examples/wasm-in-wasm",
|
"examples/wasm-in-wasm",
|
||||||
|
"examples/closures",
|
||||||
]
|
]
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
|
44
DESIGN.md
44
DESIGN.md
@ -872,6 +872,7 @@ Under the hood this generates shims that do a bunch of translation, but it
|
|||||||
suffices to say that a call in wasm to `foo` should always return
|
suffices to say that a call in wasm to `foo` should always return
|
||||||
appropriately.
|
appropriately.
|
||||||
|
|
||||||
|
|
||||||
## Customizing import behavior
|
## Customizing import behavior
|
||||||
|
|
||||||
The `#[wasm_bindgen]` macro supports a good amount of configuration for
|
The `#[wasm_bindgen]` macro supports a good amount of configuration for
|
||||||
@ -1037,6 +1038,49 @@ possibilities!
|
|||||||
All of these functions will call `console.log` in Rust, but each identifier
|
All of these functions will call `console.log` in Rust, but each identifier
|
||||||
will have only one signature in Rust.
|
will have only one signature in Rust.
|
||||||
|
|
||||||
|
## Closures
|
||||||
|
|
||||||
|
Closures are a particularly tricky topic in wasm-bindgen right now. They use
|
||||||
|
somewhat advanced language features to currently be implemented and *still* the
|
||||||
|
amount of functionality you can use is quite limiting.
|
||||||
|
|
||||||
|
Most of the implementation details of closures can be found in `src/convert.rs`
|
||||||
|
and `src/closure.rs`, effectively the `ToRefWasmBoundary` implementations for
|
||||||
|
closure types. Stack closures are pretty straightforward in that they pass
|
||||||
|
a function pointer and a data pointer to JS. This function pointer is accessed
|
||||||
|
via the exported `WebAssembly.Table` in JS, and the data pointer is passed along
|
||||||
|
eventually when the JS closure is invoked.
|
||||||
|
|
||||||
|
Stack closures currently only support `Fn` because there's no great location to
|
||||||
|
insert a `RefCell` for types like `FnMut`. This restriction may be lift-able
|
||||||
|
though in the future...
|
||||||
|
|
||||||
|
Long-lived closures are a bit more complicated. The general idea there is:
|
||||||
|
|
||||||
|
* First you create a `Closure`. This manufactures a JS callback and "passes it"
|
||||||
|
to Rust so Rust can store it.
|
||||||
|
* Next you later pass it as `&Closure<...>` to JS. This extracts the callback
|
||||||
|
from Rust and passes it to JS.
|
||||||
|
* Finally you eventually drop the Rust `Closure` which invalidates the JS
|
||||||
|
closure.
|
||||||
|
|
||||||
|
Creation of the initial JS function is done with a bunch of
|
||||||
|
`__wbindgen_cb_arityN` functions. These functions create a JS closure with the
|
||||||
|
given arity (number of arguments). This isn't really that scalable unfortunately
|
||||||
|
and also means that it's very difficult to support richer types one day. Unsure
|
||||||
|
how to solve this.
|
||||||
|
|
||||||
|
The `ToRefWasmBoundary` is quite straightforward for `Closure` as it just plucks
|
||||||
|
out the JS closure and passes it along. The real meat comes down to the
|
||||||
|
`WasmShim` internal trait. This is implemented for all the *unsized* closure
|
||||||
|
types to avoid running afoul with coherence. Each trait impl defines a shim
|
||||||
|
function to be invokeable from JS as well as the ability to wrap up the sized
|
||||||
|
verion (aka transition from `F: FnMut()` to `FnMut()`). Impls for `FnMut` also
|
||||||
|
embed the `RefCell` internally.
|
||||||
|
|
||||||
|
The `WasmShim` design is basically the first thing that got working today. It's
|
||||||
|
not great and will likely change in the future to hopefully be more flexible!
|
||||||
|
|
||||||
## Wrapping up
|
## Wrapping up
|
||||||
|
|
||||||
That's currently at least what `wasm-bindgen` has to offer! If you've got more
|
That's currently at least what `wasm-bindgen` has to offer! If you've got more
|
||||||
|
77
README.md
77
README.md
@ -24,8 +24,8 @@ Notable features of this project includes:
|
|||||||
* Importing JS functionality in to Rust such as [DOM manipulation][dom-ex],
|
* Importing JS functionality in to Rust such as [DOM manipulation][dom-ex],
|
||||||
[console logging][console-log], or [performance monitoring][perf-ex].
|
[console logging][console-log], or [performance monitoring][perf-ex].
|
||||||
* [Exporting Rust functionality][smorg-ex] to JS such as classes, functions, etc.
|
* [Exporting Rust functionality][smorg-ex] to JS such as classes, functions, etc.
|
||||||
* Working with rich types like strings, numbers, classes, and objects rather
|
* Working with rich types like strings, numbers, classes, closures, and objects
|
||||||
than simply `u32` and floats.
|
rather than simply `u32` and floats.
|
||||||
|
|
||||||
This project is still relatively new but feedback is of course always
|
This project is still relatively new but feedback is of course always
|
||||||
welcome! If you're curious about the design plus even more information about
|
welcome! If you're curious about the design plus even more information about
|
||||||
@ -397,6 +397,79 @@ export class Awesome {
|
|||||||
booted.then(main);
|
booted.then(main);
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Closures
|
||||||
|
|
||||||
|
The `#[wasm_bindgen]` attribute supports a limited subset of Rust closures being
|
||||||
|
passed to JS at this time. There are plans to expand this support currently but
|
||||||
|
it's not clear how to proceed unfortunately. In any case some examples of what
|
||||||
|
you can do are:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#[wasm_bindgen]
|
||||||
|
extern {
|
||||||
|
fn foo(a: &Fn()); // must be `Fn`, not `FnMut`
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Here a function `foo` is imported from JS where the first argument is a *stack
|
||||||
|
closure*. You can call this function with a `&Fn()` argument and JS will receive
|
||||||
|
a JS function. When the `foo` function returns, however, the JS function will be
|
||||||
|
invalidated and any future usage of it will raise an exception.
|
||||||
|
|
||||||
|
Closures also support arguments and return values native to the wasm type
|
||||||
|
system, aka f32/u32:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#[wasm_bindgen]
|
||||||
|
extern {
|
||||||
|
fn bar(a: &Fn(u32, f32) -> f64);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
At this time [types like strings aren't supported][cbstr] unfortunately.
|
||||||
|
|
||||||
|
[cbstr]: https://github.com/rustwasm/wasm-bindgen/issues/104
|
||||||
|
|
||||||
|
Sometimes the stack behavior of these closures is not desired. For example you'd
|
||||||
|
like to schedule a closure to be run on the next turn of the event loop in JS
|
||||||
|
through `setTimeout`. For this you want the imported function to return but the
|
||||||
|
JS closure still needs to be valid!
|
||||||
|
|
||||||
|
To support this use case you can also do:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use wasm_bindgen::prelude::*;
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
extern {
|
||||||
|
fn baz(a: &Closure<Fn()>);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The `Closure` type is defined in the `wasm_bindgen` crate and represents a "long
|
||||||
|
lived" closure. The JS closure passed to `baz` is still valid after `baz`
|
||||||
|
returns, and the validity of the JS closure is tied to the lifetime of the
|
||||||
|
`Closure` in Rust. Once `Closure` is dropped it will deallocate its internal
|
||||||
|
memory and invalidate the corresponding JS function.
|
||||||
|
|
||||||
|
Unlike stack closures a `Closure` supports `FnMut`:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use wasm_bindgen::prelude::*;
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
extern {
|
||||||
|
fn another(a: &Closure<FnMut() -> u32>);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Like stack closures, however, only wasm types like u32/f32 are supported today.
|
||||||
|
|
||||||
|
At this time you cannot [pass a JS closure to Rust][cbjs], you can only pass a
|
||||||
|
Rust closure to JS in limited circumstances.
|
||||||
|
|
||||||
|
[cbjs]: https://github.com/rustwasm/wasm-bindgen/issues/103
|
||||||
|
|
||||||
## Feature reference
|
## Feature reference
|
||||||
|
|
||||||
Here this section will attempt to be a reference for the various features
|
Here this section will attempt to be a reference for the various features
|
||||||
|
@ -255,6 +255,12 @@ impl<'a> Context<'a> {
|
|||||||
dropRef(i);
|
dropRef(i);
|
||||||
}")
|
}")
|
||||||
});
|
});
|
||||||
|
bind("__wbindgen_cb_forget", &|me| {
|
||||||
|
me.expose_drop_ref();
|
||||||
|
String::from("function(i) {
|
||||||
|
dropRef(i);
|
||||||
|
}")
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
self.rewrite_imports(module_name);
|
self.rewrite_imports(module_name);
|
||||||
|
@ -22,3 +22,5 @@ The examples here are:
|
|||||||
operations in Rust
|
operations in Rust
|
||||||
* `wasm-in-wasm` - how to interact with namespaced APIs like
|
* `wasm-in-wasm` - how to interact with namespaced APIs like
|
||||||
`WebAssembly.Module` and shows off creation of a WebAssembly module from Rust
|
`WebAssembly.Module` and shows off creation of a WebAssembly module from Rust
|
||||||
|
* `closures` - an example of how to invoke functions like `setInterval` or use
|
||||||
|
the `onclick` property in conjunction with closures.
|
||||||
|
4
examples/closures/.gitignore
vendored
Normal file
4
examples/closures/.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
package-lock.json
|
||||||
|
closures.js
|
||||||
|
closures_bg.js
|
||||||
|
closures_bg.wasm
|
10
examples/closures/Cargo.toml
Normal file
10
examples/closures/Cargo.toml
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
[package]
|
||||||
|
name = "closures"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Alex Crichton <alex@alexcrichton.com>"]
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
crate-type = ["cdylib"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
wasm-bindgen = { path = "../.." }
|
20
examples/closures/README.md
Normal file
20
examples/closures/README.md
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
# Closure examples
|
||||||
|
|
||||||
|
This directory is an example of using the `#[wasm_bindgen]` macro with closures
|
||||||
|
to interact with the DOM.
|
||||||
|
|
||||||
|
You can build the example with:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ ./build.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
(or running the commands on Windows manually)
|
||||||
|
|
||||||
|
and then opening up `index.html` in a web browser should show a hello message on
|
||||||
|
the web page generated by the wasm.
|
||||||
|
|
||||||
|
For more information about this example be sure to check out
|
||||||
|
[`hello_world`][hello] which also has more comments about caveats and such.
|
||||||
|
|
||||||
|
[hello]: https://github.com/alexcrichton/wasm-bindgen/tree/master/examples/hello_world
|
12
examples/closures/build.sh
Executable file
12
examples/closures/build.sh
Executable file
@ -0,0 +1,12 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# For more coments about what's going on here, see the `hello_world` example
|
||||||
|
|
||||||
|
set -ex
|
||||||
|
|
||||||
|
cargo +nightly build --target wasm32-unknown-unknown
|
||||||
|
cargo +nightly run --manifest-path ../../crates/cli/Cargo.toml \
|
||||||
|
--bin wasm-bindgen -- \
|
||||||
|
../../target/wasm32-unknown-unknown/debug/closures.wasm --out-dir .
|
||||||
|
#npm install
|
||||||
|
npm run serve
|
40
examples/closures/index.html
Normal file
40
examples/closures/index.html
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta content="text/html;charset=utf-8" http-equiv="Content-Type"/>
|
||||||
|
<style>
|
||||||
|
#green-square {
|
||||||
|
background: green;
|
||||||
|
width: 200px;
|
||||||
|
height: 200px;
|
||||||
|
text-align: center;
|
||||||
|
line-height: 200px;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
#green-square span {
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id='loading'>
|
||||||
|
Loading...
|
||||||
|
</div>
|
||||||
|
<div id='script' style='display:none'>
|
||||||
|
<p>
|
||||||
|
The current time is:
|
||||||
|
<span id='current-time'>...</span>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div id='green-square'>
|
||||||
|
<span>Click me!</span>
|
||||||
|
</div>
|
||||||
|
<p>
|
||||||
|
You've clicked the green square
|
||||||
|
<span id='num-clicks'>0</span>
|
||||||
|
times
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<script src='./index.js'></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
4
examples/closures/index.js
Normal file
4
examples/closures/index.js
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
// For more comments about what's going on here, check out the `hello_world`
|
||||||
|
// example
|
||||||
|
const rust = import("./closures");
|
||||||
|
rust.then(m => m.run());
|
10
examples/closures/package.json
Normal file
10
examples/closures/package.json
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"scripts": {
|
||||||
|
"serve": "webpack-dev-server"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"webpack": "^4.0.1",
|
||||||
|
"webpack-cli": "^2.0.10",
|
||||||
|
"webpack-dev-server": "^3.1.0"
|
||||||
|
}
|
||||||
|
}
|
94
examples/closures/src/lib.rs
Normal file
94
examples/closures/src/lib.rs
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
#![feature(proc_macro, wasm_custom_section, wasm_import_module)]
|
||||||
|
|
||||||
|
extern crate wasm_bindgen;
|
||||||
|
|
||||||
|
use wasm_bindgen::prelude::*;
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
extern {
|
||||||
|
// Binding for the `setInverval` method in JS. This function takes a "long
|
||||||
|
// lived" closure as the first argument so we use `Closure` instead of
|
||||||
|
// a bare `&Fn()` which only surives for that one stack frame.
|
||||||
|
//
|
||||||
|
// The second argument is then the interval and the return value is how we
|
||||||
|
// clear this interval. We're not going to clear our interval in this
|
||||||
|
// example though so the return value is ignored.
|
||||||
|
#[wasm_bindgen(js_name = setInterval)]
|
||||||
|
fn set_interval(cb: &Closure<FnMut()>, delay: u32) -> f64;
|
||||||
|
|
||||||
|
// Bindings for JS `Date` so we can update our local timer
|
||||||
|
type Date;
|
||||||
|
#[wasm_bindgen(constructor)]
|
||||||
|
fn new() -> Date;
|
||||||
|
#[wasm_bindgen(method, js_name = toLocaleString)]
|
||||||
|
fn to_locale_string(this: &Date) -> String;
|
||||||
|
|
||||||
|
// Bindings for `document` and various methods of updating HTML elements.
|
||||||
|
// Like with the `dom` example these'll ideally be upstream in a generated
|
||||||
|
// crate one day but for now we manually define them.
|
||||||
|
type HTMLDocument;
|
||||||
|
static document: HTMLDocument;
|
||||||
|
#[wasm_bindgen(method, js_name = getElementById)]
|
||||||
|
fn get_element_by_id(this: &HTMLDocument, id: &str) -> Element;
|
||||||
|
#[wasm_bindgen(method, js_name = getElementById)]
|
||||||
|
fn get_html_element_by_id(this: &HTMLDocument, id: &str) -> HTMLElement;
|
||||||
|
|
||||||
|
type Element;
|
||||||
|
#[wasm_bindgen(method, setter = innerHTML)]
|
||||||
|
fn set_inner_html(this: &Element, html: &str);
|
||||||
|
|
||||||
|
type HTMLElement;
|
||||||
|
#[wasm_bindgen(method, setter)]
|
||||||
|
fn set_onclick(this: &HTMLElement, cb: &Closure<FnMut()>);
|
||||||
|
#[wasm_bindgen(method, getter)]
|
||||||
|
fn style(this: &HTMLElement) -> CSS2Properties;
|
||||||
|
|
||||||
|
type CSS2Properties;
|
||||||
|
#[wasm_bindgen(method, setter)]
|
||||||
|
fn set_display(this: &CSS2Properties, display: &str);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
pub fn run() {
|
||||||
|
// Set up a clock on our page and update it each second to ensure it's got
|
||||||
|
// an accurate date.
|
||||||
|
let a = Closure::new(update_time);
|
||||||
|
set_interval(&a, 1000);
|
||||||
|
update_time();
|
||||||
|
fn update_time() {
|
||||||
|
document.get_element_by_id("current-time")
|
||||||
|
.set_inner_html(&Date::new().to_locale_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
// We also want to count the number of times that our green square has been
|
||||||
|
// clicked. Our callback will update the `#num-clicks` div
|
||||||
|
let square = document.get_html_element_by_id("green-square");
|
||||||
|
let mut clicks = 0;
|
||||||
|
let b = Closure::new(move || {
|
||||||
|
clicks += 1;
|
||||||
|
document.get_element_by_id("num-clicks")
|
||||||
|
.set_inner_html(&clicks.to_string());
|
||||||
|
});
|
||||||
|
square.set_onclick(&b);
|
||||||
|
|
||||||
|
// The instances of `Closure` that we created will invalidate their
|
||||||
|
// corresponding JS callback whenever they're dropped, so if we were to
|
||||||
|
// normally return from `run` then both of our registered closures will
|
||||||
|
// raise exceptions when invoked.
|
||||||
|
//
|
||||||
|
// Normally we'd store these handles to later get dropped at an appropriate
|
||||||
|
// time but for now we want these to be global handlers so we use the
|
||||||
|
// `forget` method to drop them without invalidating the closure. Note that
|
||||||
|
// this is leaking memory in Rust, so this should be done judiciously!
|
||||||
|
a.forget();
|
||||||
|
b.forget();
|
||||||
|
|
||||||
|
// And finally now that our demo is ready to go let's switch things up so
|
||||||
|
// everything is displayed and our loading prompt is hidden.
|
||||||
|
document.get_html_element_by_id("loading")
|
||||||
|
.style()
|
||||||
|
.set_display("none");
|
||||||
|
document.get_html_element_by_id("script")
|
||||||
|
.style()
|
||||||
|
.set_display("block");
|
||||||
|
}
|
10
examples/closures/webpack.config.js
Normal file
10
examples/closures/webpack.config.js
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
entry: "./index.js",
|
||||||
|
output: {
|
||||||
|
path: path.resolve(__dirname, "dist"),
|
||||||
|
filename: "index.js",
|
||||||
|
},
|
||||||
|
mode: "development"
|
||||||
|
};
|
226
src/closure.rs
226
src/closure.rs
@ -1,11 +1,155 @@
|
|||||||
use std::mem::ManuallyDrop;
|
//! Support for long-lived closures in `wasm-bindgen`
|
||||||
use std::marker::{self, Unsize};
|
//!
|
||||||
|
//! This module defines the `Closure` type which is used to pass "owned
|
||||||
|
//! closures" from Rust to JS. Some more details can be found on the `Closure`
|
||||||
|
//! type itself.
|
||||||
|
|
||||||
|
use std::mem::{self, ManuallyDrop};
|
||||||
|
use std::marker::Unsize;
|
||||||
|
|
||||||
use {throw, JsValue};
|
use {throw, JsValue};
|
||||||
use convert::*;
|
use convert::*;
|
||||||
use __rt::WasmRefCell;
|
use __rt::WasmRefCell;
|
||||||
|
|
||||||
pub unsafe trait WasmShim<'a> {
|
/// A handle to both a closure in Rust as well as JS closure which will invoke
|
||||||
|
/// the Rust closure.
|
||||||
|
///
|
||||||
|
/// A `Closure` is the primary way that a `'static` lifetime closure is
|
||||||
|
/// transferred from Rust to JS. `Closure` currently requires that the closures
|
||||||
|
/// it's created with have the `'static` lifetime in Rust for soundness reasons.
|
||||||
|
///
|
||||||
|
/// This type is a "handle" in the sense that whenever it is dropped it will
|
||||||
|
/// invalidate the JS closure that it refers to. Any usage of the closure in JS
|
||||||
|
/// after the `Closure` has been dropped will raise an exception. It's then up
|
||||||
|
/// to you to arrange for `Closure` to be properly deallocate at an appropriate
|
||||||
|
/// location in your program.
|
||||||
|
///
|
||||||
|
/// The type parameter on `Closure` is the type of closure that this represents.
|
||||||
|
/// Currently this can only be the `Fn` and `FnMut` traits with up to 7
|
||||||
|
/// arguments (and an optional return value). The arguments/return value of the
|
||||||
|
/// trait must be numbers like `u32` for now, although this restriction may be
|
||||||
|
/// lifted in the future!
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```rust,no_run
|
||||||
|
/// #[wasm_bindgen]
|
||||||
|
/// extern {
|
||||||
|
/// fn setTimeout(closure: &Closure<FnMut()>, time: u32);
|
||||||
|
///
|
||||||
|
/// #[wasm_bindgen(js_namespace = console)]
|
||||||
|
/// fn log(s: &str);
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// #[wasm_bindgen]
|
||||||
|
/// pub struct ClosureHandle(Closure<FnMut()>);
|
||||||
|
///
|
||||||
|
/// #[wasm_bindgen]
|
||||||
|
/// pub fn run() -> ClosureHandle {
|
||||||
|
/// // First up we use `Closure::new` to wrap up a Rust closure and create
|
||||||
|
/// a JS closure.
|
||||||
|
/// let cb = Closure::new(|| {
|
||||||
|
/// log("timeout elapsed!");
|
||||||
|
/// });
|
||||||
|
///
|
||||||
|
/// // Next we pass this via reference to the `setTimeout` function, and
|
||||||
|
/// // `setTimeout` gets a handle to the corresponding JS closure.
|
||||||
|
/// setTimeout(&cb, 1_000);
|
||||||
|
///
|
||||||
|
/// // If we were to drop `cb` here it would cause an exception to be raised
|
||||||
|
/// // when the timeout elapses. Instead we *return* our handle back to JS
|
||||||
|
/// // so JS can tell us later when it would like to deallocate this handle.
|
||||||
|
/// ClosureHandle(cb)
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
pub struct Closure<T: WasmShim + ?Sized> {
|
||||||
|
_inner: T::Wrapper,
|
||||||
|
js: ManuallyDrop<JsValue>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Closure<T>
|
||||||
|
where T: WasmShim + ?Sized,
|
||||||
|
{
|
||||||
|
/// Creates a new instance of `Closure` from the provided Rust closure.
|
||||||
|
///
|
||||||
|
/// Note that the closure provided here, `F`, has a few requirements
|
||||||
|
/// associated with it:
|
||||||
|
///
|
||||||
|
/// * It must implement `Fn` or `FnMut`
|
||||||
|
/// * It must be `'static`, aka no stack references (use the `move` keyword)
|
||||||
|
/// * It can have at most 7 arguments
|
||||||
|
/// * Its arguments and return values are all wasm types like u32/f64.
|
||||||
|
///
|
||||||
|
/// This is unfortunately pretty restrictive for now but hopefully some of
|
||||||
|
/// these restrictions can be lifted in the future!
|
||||||
|
pub fn new<F>(t: F) -> Closure<T>
|
||||||
|
where F: Unsize<T> + 'static
|
||||||
|
{
|
||||||
|
Closure::wrap(T::wrap(t))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A mostly internal function to wrap a boxed closure inside a `Closure`
|
||||||
|
/// type.
|
||||||
|
///
|
||||||
|
/// This is the function where the JS closure is manufactured.
|
||||||
|
pub fn wrap(t: T::Wrapper) -> Closure<T> {
|
||||||
|
unsafe {
|
||||||
|
let data = T::data(&t);
|
||||||
|
let js = T::factory()(T::shim(), data[0], data[1]);
|
||||||
|
Closure {
|
||||||
|
_inner: t,
|
||||||
|
js: ManuallyDrop::new(JsValue::from_abi(js, &mut GlobalStack::new())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Leaks this `Closure` to ensure it remains valid for the duration of the
|
||||||
|
/// entire program.
|
||||||
|
///
|
||||||
|
/// > **Note**: this function will leak memory. It should be used sparingly
|
||||||
|
/// > to ensure the memory leak doesn't affect the program too much.
|
||||||
|
///
|
||||||
|
/// When a `Closure` is dropped it will invalidate the associated JS
|
||||||
|
/// closure, but this isn't always desired. Some callbacks are alive for
|
||||||
|
/// the entire duration of the program, so this can be used to conveniently
|
||||||
|
/// leak this instance of `Closure` while performing as much internal
|
||||||
|
/// cleanup as it can.
|
||||||
|
pub fn forget(self) {
|
||||||
|
unsafe {
|
||||||
|
super::__wbindgen_cb_forget(self.js.to_abi_ref(&mut GlobalStack::new()));
|
||||||
|
mem::forget(self);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// `Closure` can only be passed by reference to imports.
|
||||||
|
impl<T> ToRefWasmBoundary for Closure<T>
|
||||||
|
where T: WasmShim + ?Sized,
|
||||||
|
{
|
||||||
|
type Abi = u32;
|
||||||
|
const DESCRIPTOR: Descriptor = T::DESCRIPTOR;
|
||||||
|
|
||||||
|
fn to_abi_ref(&self, extra: &mut Stack) -> u32 {
|
||||||
|
self.js.to_abi_ref(extra)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Drop for Closure<T>
|
||||||
|
where T: WasmShim + ?Sized,
|
||||||
|
{
|
||||||
|
fn drop(&mut self) {
|
||||||
|
unsafe {
|
||||||
|
let idx = self.js.to_abi_ref(&mut GlobalStack::new());
|
||||||
|
super::__wbindgen_cb_drop(idx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An internal trait for the `Closure` type.
|
||||||
|
///
|
||||||
|
/// This trait is not stable and it's not recommended to use this in bounds or
|
||||||
|
/// implement yourself.
|
||||||
|
pub unsafe trait WasmShim {
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
const DESCRIPTOR: Descriptor;
|
const DESCRIPTOR: Descriptor;
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
@ -15,7 +159,7 @@ pub unsafe trait WasmShim<'a> {
|
|||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
fn factory() -> unsafe extern fn(u32, u32, u32) -> u32;
|
fn factory() -> unsafe extern fn(u32, u32, u32) -> u32;
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
fn wrap<U>(u: U) -> Self::Wrapper where U: Unsize<Self> + 'a;
|
fn wrap<U>(u: U) -> Self::Wrapper where U: Unsize<Self> + 'static;
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
fn data(t: &Self::Wrapper) -> [u32; 2];
|
fn data(t: &Self::Wrapper) -> [u32; 2];
|
||||||
}
|
}
|
||||||
@ -30,9 +174,9 @@ macro_rules! doit {
|
|||||||
($($var:ident)*) => $arity:ident
|
($($var:ident)*) => $arity:ident
|
||||||
)*) => ($(
|
)*) => ($(
|
||||||
// Fn with no return
|
// Fn with no return
|
||||||
unsafe impl<'a, $($var: WasmAbi),*> WasmShim<'a> for Fn($($var),*) + 'a {
|
unsafe impl<$($var: WasmAbi),*> WasmShim for Fn($($var),*) {
|
||||||
const DESCRIPTOR: Descriptor = DESCRIPTOR_FUNC;
|
const DESCRIPTOR: Descriptor = DESCRIPTOR_FUNC;
|
||||||
type Wrapper = Box<Fn($($var),*) + 'a>;
|
type Wrapper = Box<Fn($($var),*)>;
|
||||||
|
|
||||||
fn shim() -> u32 {
|
fn shim() -> u32 {
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
@ -53,7 +197,7 @@ macro_rules! doit {
|
|||||||
super::$arity
|
super::$arity
|
||||||
}
|
}
|
||||||
|
|
||||||
fn wrap<U>(u: U) -> Self::Wrapper where U: Unsize<Self> + 'a {
|
fn wrap<U>(u: U) -> Self::Wrapper where U: Unsize<Self> + 'static {
|
||||||
Box::new(u) as Box<Self>
|
Box::new(u) as Box<Self>
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -65,9 +209,9 @@ macro_rules! doit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Fn with a return
|
// Fn with a return
|
||||||
unsafe impl<'a, $($var: WasmAbi,)* R: WasmAbi> WasmShim<'a> for Fn($($var),*) -> R + 'a {
|
unsafe impl<$($var: WasmAbi,)* R: WasmAbi> WasmShim for Fn($($var),*) -> R {
|
||||||
const DESCRIPTOR: Descriptor = DESCRIPTOR_FUNC;
|
const DESCRIPTOR: Descriptor = DESCRIPTOR_FUNC;
|
||||||
type Wrapper = Box<Fn($($var),*) -> R + 'a>;
|
type Wrapper = Box<Fn($($var),*) -> R>;
|
||||||
|
|
||||||
fn shim() -> u32 {
|
fn shim() -> u32 {
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
@ -88,7 +232,7 @@ macro_rules! doit {
|
|||||||
super::$arity
|
super::$arity
|
||||||
}
|
}
|
||||||
|
|
||||||
fn wrap<U>(u: U) -> Self::Wrapper where U: Unsize<Self> + 'a {
|
fn wrap<U>(u: U) -> Self::Wrapper where U: Unsize<Self> + 'static {
|
||||||
Box::new(u) as Box<Self>
|
Box::new(u) as Box<Self>
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -100,9 +244,9 @@ macro_rules! doit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// FnMut with no return
|
// FnMut with no return
|
||||||
unsafe impl<'a, $($var: WasmAbi),*> WasmShim<'a> for FnMut($($var),*) + 'a {
|
unsafe impl<$($var: WasmAbi),*> WasmShim for FnMut($($var),*) {
|
||||||
const DESCRIPTOR: Descriptor = DESCRIPTOR_FUNC;
|
const DESCRIPTOR: Descriptor = DESCRIPTOR_FUNC;
|
||||||
type Wrapper = Box<WasmRefCell<FnMut($($var),*) + 'a>>;
|
type Wrapper = Box<WasmRefCell<FnMut($($var),*)>>;
|
||||||
|
|
||||||
fn shim() -> u32 {
|
fn shim() -> u32 {
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
@ -127,7 +271,7 @@ macro_rules! doit {
|
|||||||
super::$arity
|
super::$arity
|
||||||
}
|
}
|
||||||
|
|
||||||
fn wrap<U>(u: U) -> Self::Wrapper where U: Unsize<Self> + 'a {
|
fn wrap<U>(u: U) -> Self::Wrapper where U: Unsize<Self> + 'static {
|
||||||
Box::new(WasmRefCell::new(u)) as Box<_>
|
Box::new(WasmRefCell::new(u)) as Box<_>
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -139,9 +283,9 @@ macro_rules! doit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// FnMut with a return
|
// FnMut with a return
|
||||||
unsafe impl<'a, $($var: WasmAbi,)* R: WasmAbi> WasmShim<'a> for FnMut($($var),*) -> R + 'a {
|
unsafe impl<$($var: WasmAbi,)* R: WasmAbi> WasmShim for FnMut($($var),*) -> R {
|
||||||
const DESCRIPTOR: Descriptor = DESCRIPTOR_FUNC;
|
const DESCRIPTOR: Descriptor = DESCRIPTOR_FUNC;
|
||||||
type Wrapper = Box<WasmRefCell<FnMut($($var),*) -> R + 'a>>;
|
type Wrapper = Box<WasmRefCell<FnMut($($var),*) -> R>>;
|
||||||
|
|
||||||
fn shim() -> u32 {
|
fn shim() -> u32 {
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
@ -166,7 +310,7 @@ macro_rules! doit {
|
|||||||
super::$arity
|
super::$arity
|
||||||
}
|
}
|
||||||
|
|
||||||
fn wrap<U>(u: U) -> Self::Wrapper where U: Unsize<Self> + 'a {
|
fn wrap<U>(u: U) -> Self::Wrapper where U: Unsize<Self> + 'static {
|
||||||
Box::new(WasmRefCell::new(u)) as Box<_>
|
Box::new(WasmRefCell::new(u)) as Box<_>
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -189,53 +333,3 @@ doit! {
|
|||||||
(A B C D E F) => __wbindgen_cb_arity6
|
(A B C D E F) => __wbindgen_cb_arity6
|
||||||
(A B C D E F G) => __wbindgen_cb_arity7
|
(A B C D E F G) => __wbindgen_cb_arity7
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Closure<'a, T: WasmShim<'a> + ?Sized + 'a> {
|
|
||||||
_inner: T::Wrapper,
|
|
||||||
js: ManuallyDrop<JsValue>,
|
|
||||||
_marker: marker::PhantomData<&'a ()>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a, T> Closure<'a, T>
|
|
||||||
where T: WasmShim<'a> + ?Sized,
|
|
||||||
{
|
|
||||||
pub fn new<U>(t: U) -> Closure<'a, T>
|
|
||||||
where U: Unsize<T> + 'a
|
|
||||||
{
|
|
||||||
Closure::wrap(T::wrap(t))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn wrap(t: T::Wrapper) -> Closure<'a, T> {
|
|
||||||
unsafe {
|
|
||||||
let data = T::data(&t);
|
|
||||||
let js = T::factory()(T::shim(), data[0], data[1]);
|
|
||||||
Closure {
|
|
||||||
_inner: t,
|
|
||||||
js: ManuallyDrop::new(JsValue::from_abi(js, &mut GlobalStack::new())),
|
|
||||||
_marker: marker::PhantomData,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a, T> ToRefWasmBoundary for Closure<'a, T>
|
|
||||||
where T: WasmShim<'a> + ?Sized,
|
|
||||||
{
|
|
||||||
type Abi = u32;
|
|
||||||
const DESCRIPTOR: Descriptor = T::DESCRIPTOR;
|
|
||||||
|
|
||||||
fn to_abi_ref(&self, extra: &mut Stack) -> u32 {
|
|
||||||
self.js.to_abi_ref(extra)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a, T> Drop for Closure<'a, T>
|
|
||||||
where T: WasmShim<'a> + ?Sized,
|
|
||||||
{
|
|
||||||
fn drop(&mut self) {
|
|
||||||
unsafe {
|
|
||||||
let idx = self.js.to_abi_ref(&mut GlobalStack::new());
|
|
||||||
super::__wbindgen_cb_drop(idx);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -242,6 +242,7 @@ extern {
|
|||||||
fn __wbindgen_cb_arity6(a: u32, b: u32, c: u32) -> u32;
|
fn __wbindgen_cb_arity6(a: u32, b: u32, c: u32) -> u32;
|
||||||
fn __wbindgen_cb_arity7(a: u32, b: u32, c: u32) -> u32;
|
fn __wbindgen_cb_arity7(a: u32, b: u32, c: u32) -> u32;
|
||||||
fn __wbindgen_cb_drop(idx: u32);
|
fn __wbindgen_cb_drop(idx: u32);
|
||||||
|
fn __wbindgen_cb_forget(idx: u32);
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Clone for JsValue {
|
impl Clone for JsValue {
|
||||||
|
Loading…
Reference in New Issue
Block a user