Expand description
This is an experimental implementation of a web-based async runtime. Web as in “browser”.
Ideally, with this crate, the only JavaScript code you’ll need to write would be something like this:
import * as wasm from "my-wasm-crate";
import * as style from "./style.css";
wasm.main();
That’s it (ignoring webpack configuration file, of course).
Examples
Asynchronous “Is Prime” Test
I know this is not the best example, because primality test is CPU-bound, but I wanted to show an example of CPU-bound tasks running fast in WASM without blocking the browser, i.e. by pausing periodically and giving control back to browser by a few milliseconds.
The full example can be seen in examples/isprime directory.
A few numbers to try: 7399329281, 2199023255551, 9410454606139,
64954802446103, 340845657750593, 576460752303423487,
2305843009213693951.
use std::time::Duration;
use num::{BigUint, Zero};
use wasm_bindgen::JsValue;
use webio::event::{self, EventType};
/// Number of steps before yielding control back to browser when testing
/// whether a number is prime or not, in order not to freeze the browser with
/// computations on large numbers. Of course, yielding back to the browser is
/// just a pause, so after a few milliseconds later, WASM can resume its job on
/// the current number.
///
/// However, note that this applies only when a number is being tested,
/// otherwise WASM sleeps and won't wake up until the button is pressed.
const YIELD_STEPS: u16 = 20000;
/// Tests if the given number is prime, asynchronous because it will pause the
/// execution after some steps.
async fn is_prime(number: &BigUint) -> bool {
let two = BigUint::from(2u8);
if *number < two {
return false;
}
if *number == two {
return true;
}
if (number % &two).is_zero() {
return false;
}
let mut attempt = BigUint::from(3u8);
let mut square = &attempt * &attempt;
while square <= *number {
if (number % &attempt).is_zero() {
return false;
}
if (&attempt / &two % YIELD_STEPS).is_zero() {
webio::time::timeout(Duration::from_millis(10)).await;
}
attempt += &two;
square = &attempt * &attempt;
}
true
}
/// Main function of this WASM application.
#[webio::main]
pub async fn app_main() {
// Gets all necessary HTML elements.
let document = web_sys::window().unwrap().document().unwrap();
let input_raw = document.get_element_by_id("input").unwrap();
let input = web_sys::HtmlInputElement::from(JsValue::from(input_raw));
let button = document.get_element_by_id("button").unwrap();
let answer_elem = document.get_element_by_id("answer").unwrap();
// Sets a listener for the click event on the button.
let listener = event::Click.add_listener(&button);
loop {
// No problem being an infinite loop because it is asynchronous.
// It won't block the browser.
//
// This will sleep until the user press a button.
listener.listen_next().await.unwrap();
// Cleans up previous message.
answer_elem.set_text_content(Some("Loading..."));
// Gets and validates input.
let number: BigUint = match input.value().parse() {
Ok(number) => number,
Err(_) => {
answer_elem.set_text_content(Some("Invalid input!"));
continue;
},
};
// Runs and tells the user the correct answer.
if is_prime(&number).await {
answer_elem.set_text_content(Some("Yes"));
} else {
answer_elem.set_text_content(Some("No"));
}
}
}Modules
- This module defines utilities for translating a callback into asynchronous functions.
- event
eventModule for listening and handling JS events from Rust. - This module provides locks to be used within a single instance of a Rust WebAssembly module. Why locks given that a WASM module is single-threaded? Well, when using async Rust, a critical operation can be split by an
.awaitexpression. In fact, these locks are designed specially for that: ensuring a critical operation is performed as if it were atomic even if you insert an.awaitbetween two steps. - This module exports items related to task spawning.
- time
timeThis module implements time-related utilities.
Macros
- console
macrosPrints to the JavaScript/browser/node console using a given method. - Debugs to the JavaScript/browser/node console using a given method. Syntax:
- Shows error in the JavaScript/browser/node console using a given method.
- Shows info in the JavaScript/browser/node console using a given method.
- Logs to the JavaScript/browser/node console using a given method. Syntax:
- Warns to the JavaScript/browser/node console using a given method. Syntax:
- join
macrosJoins a list of futures and returns their output into a tuple in the same order that the futures were given. Futures must be'static. - Flags a test file as running in the browser instead of node.
- select
macrosListens to a list of futures and finishes when the first future finishes, which is then selected. Every future is placed in a “match arm”, and when it is selected, the “arm” pattern is matched and the macro evaluates to the right side of the “arm”. Patterns must be irrefutable, typically just a variable name, or destructuring. Futures must be'static. - try_join
macrosJoins a list of futures and returns their output into a tuple in the same order that the futures were given, but if one of them fails,try_joinfails, and so a result of tuples is returned. Futures must be'static.
Attribute Macros
- main
macrosThis macro converts an asynchronous main function into a synchronous one that can actually be an entry point, and that invokes the asynchronous code. Under the hood, the asynchronous code is detached from the current call. - test
macrosThis macro converts an asynchronous test function into a synchronous one that can actually be tested bywasm_bindgen_test, and that invokes the asynchronous code. Under the hood, the asynchronous code is detached from the current call.
Derive Macros
- EventType
macrosandeventDefines a custom event wrapper, with the intention of being safe. It is up to the caller type, however, to ensure that name is correct for the given event data type.