From 7dd16766eb3d8fd03e284769bf422006e46973fd Mon Sep 17 00:00:00 2001 From: sakex Date: Thu, 17 Aug 2023 18:26:17 +0200 Subject: [PATCH] More concise class --- book.toml | 4 +- src/SUMMARY.md | 9 +- src/exercises/webassembly/webassembly.md | 12 +++ src/rust-wasm-template/Cargo.toml | 5 +- src/rust-wasm-template/src/lib.rs | 32 +++---- .../static/exercises/game_of_life/index.mjs | 24 +++++ src/rust-wasm-template/static/index.mjs | 5 +- src/webassembly/async.md | 80 ++++------------- src/webassembly/error-handling.md | 7 ++ .../exported-methods.md} | 5 +- .../imported-methods.md} | 0 src/webassembly/expose-method.md | 4 +- src/webassembly/expose-rust-type.md | 31 ++----- src/webassembly/import-js-type.md | 90 +++++++++---------- src/webassembly/import-method.md | 12 ++- src/webassembly/import-method/web-sys.md | 24 +++-- src/webassembly/limitations/borrow-checker.md | 56 ++++-------- src/webassembly/limitations/closures.md | 32 +++---- 18 files changed, 187 insertions(+), 245 deletions(-) create mode 100644 src/rust-wasm-template/static/exercises/game_of_life/index.mjs create mode 100644 src/webassembly/error-handling.md rename src/webassembly/{expose-method/error-handling.md => error-handling/exported-methods.md} (92%) rename src/webassembly/{import-method/error-handling.md => error-handling/imported-methods.md} (100%) diff --git a/book.toml b/book.toml index 493a3c5d3a1e..7ebe1b564ae4 100644 --- a/book.toml +++ b/book.toml @@ -23,8 +23,8 @@ class = "bob" # pages. This will show you an estimate of what the course # participants can see during the presentation. # -# [preprocessor.aspect-ratio-helper] -# command = "./aspect-ratio-helper.py" +[preprocessor.aspect-ratio-helper] +command = "./aspect-ratio-helper.py" [output.html] curly-quotes = true diff --git a/src/SUMMARY.md b/src/SUMMARY.md index 08f4145e4006..71b08b6c529d 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -299,18 +299,20 @@ - [WebAssembly basics](webassembly.md) - [Load a Wasm module](webassembly/load-wasm-module.md) - [Expose a method](webassembly/expose-method.md) - - [Error handling for exposed methods](webassembly/expose-method/error-handling.md) - [Import Method](webassembly/import-method.md) - - [Error handling for imported methods](webassembly/import-method/error-handling.md) - [web-sys](webassembly/import-method/web-sys.md) - [Expose user-defined Rust types](webassembly/expose-rust-type.md) - [Import user-defined Javascript types](webassembly/import-js-type.md) +- [Error handling](webassembly/error-handling.md) + - [Error handling for imported methods](webassembly/error-handling/imported-methods.md) + - [Error handling for exported methods](webassembly/error-handling/exported-methods.md) - [Limitations](webassembly/limitations.md) - [Borrow Checker](webassembly/limitations/borrow-checker.md) - [Closures](webassembly/limitations/closures.md) - [Async](webassembly/async.md) - [Exercises](exercises/webassembly/webassembly.md) - [Camera](exercises/webassembly/camera.md) + - [Game Of Life](exercises/webassembly/game-of-life.md) # Final Words @@ -335,3 +337,6 @@ - [Bare Metal Rust Afternoon](exercises/bare-metal/solutions-afternoon.md) - [Concurrency Morning](exercises/concurrency/solutions-morning.md) - [Concurrency Afternoon](exercises/concurrency/solutions-afternoon.md) + - [Webassembly](exercises/webassembly/webassembly.md) + - [Camera](exercises/webassembly/solutions-camera.md) + - [Game Of Life](exercises/webassembly/solutions-game-of-life.md) \ No newline at end of file diff --git a/src/exercises/webassembly/webassembly.md b/src/exercises/webassembly/webassembly.md index eba3257d21a7..4e4fa01da53a 100644 --- a/src/exercises/webassembly/webassembly.md +++ b/src/exercises/webassembly/webassembly.md @@ -1 +1,13 @@ # Exercises + +There are two exercises for Webassembly, they both live in their own repository inside of +[rust-wasm-template](../rust-wasm-template). + +- [The Camera Exercise](camera.md) will give you access to the camera on your computer and offer you to + apply transformations on the frames it captures. +- [The Game Of Life Exercise](game-of-life.md) will have you implement _John Conway's Game Of Life_ using Webassembly. + +You can find the solutions here: + +- [Camera](solutions-camera.md) +- [Game Of Life](solutions-game-of-life.md) diff --git a/src/rust-wasm-template/Cargo.toml b/src/rust-wasm-template/Cargo.toml index 4182779c6b1c..fb9cf65e4c0a 100644 --- a/src/rust-wasm-template/Cargo.toml +++ b/src/rust-wasm-template/Cargo.toml @@ -22,14 +22,15 @@ tokio = { version = "1.29.1", features = ["sync"] } [dependencies.web-sys] version = "0.3.4" features = [ + 'CanvasRenderingContext2d', 'CssStyleDeclaration', 'Document', 'Element', 'ImageData', - 'CanvasRenderingContext2d', 'HtmlCanvasElement', - 'HtmlSelectElement', 'HtmlElement', + 'HtmlSelectElement', 'Node', + 'Response', 'Window', ] diff --git a/src/rust-wasm-template/src/lib.rs b/src/rust-wasm-template/src/lib.rs index d0af5ebec15f..ef632ec84f24 100644 --- a/src/rust-wasm-template/src/lib.rs +++ b/src/rust-wasm-template/src/lib.rs @@ -1,25 +1,15 @@ -extern crate console_error_panic_hook; - use wasm_bindgen::prelude::*; - #[wasm_bindgen] extern "C" { - fn alert(s: &str); - - #[wasm_bindgen(js_namespace = console)] - pub fn log(s: &str); -} - -#[wasm_bindgen] -pub fn set_panic_hook() { - // Generates better error messages if our code ever panics. - // - // For more details see - // https://github.com/rustwasm/console_error_panic_hook#readme - console_error_panic_hook::set_once(); -} - -#[wasm_bindgen] -pub fn add(a: i32, b: i32) -> i32 { - a + b + pub type OutputBox; + #[wasm_bindgen(constructor)] + pub fn new(element: web_sys::HtmlElement) -> OutputBox; + #[wasm_bindgen(method)] + pub fn setText(this: &OutputBox, text: &str); + #[wasm_bindgen(method, getter)] + pub fn lastText(this: &OutputBox) -> Option; + #[wasm_bindgen(method, setter)] + pub fn set_lastText(this: &OutputBox, text: Option); + #[wasm_bindgen(method, getter)] + pub fn currentText(this: &OutputBox) -> String; } diff --git a/src/rust-wasm-template/static/exercises/game_of_life/index.mjs b/src/rust-wasm-template/static/exercises/game_of_life/index.mjs new file mode 100644 index 000000000000..afc69fce5577 --- /dev/null +++ b/src/rust-wasm-template/static/exercises/game_of_life/index.mjs @@ -0,0 +1,24 @@ +import init, {setup, GameOfLife} from '/wasm/project.js'; + + +(async () => { + // Run the init method to initiate the WebAssembly module. + await init(); + setup(); + const canvas = document.getElementById('game-of-life-canvas'); + const ctx = canvas.getContext('2d'); + const game = new GameOfLife(ctx, canvas.width, canvas.height); + const startButton = document.querySelector('#start-button'); + let handle = null; + let playing = false; + startButton.addEventListener('click', () => { + if (handle === null) { + handle = game.start(); + playing = true; + } else { + handle.stop_or_start(); + playing = !playing; + } + startButton.textContent = playing ? 'STOP' : 'START'; + }); +})(); diff --git a/src/rust-wasm-template/static/index.mjs b/src/rust-wasm-template/static/index.mjs index 15bc9a9836cb..71fd9243d0b4 100644 --- a/src/rust-wasm-template/static/index.mjs +++ b/src/rust-wasm-template/static/index.mjs @@ -1,8 +1,7 @@ -import init, {set_panic_hook, add} from '/wasm/project.js'; +import init, { get_current_page} from '/wasm/project.js'; (async () => { // Run the init method to initiate the WebAssembly module. await init(); - set_panic_hook(); - document.querySelector("#wasmoutput").innerHTML = add(1, 2); + console.log(await get_current_page()); })(); diff --git a/src/webassembly/async.md b/src/webassembly/async.md index 7c4b104bf9cc..7af6d6a5310f 100644 --- a/src/webassembly/async.md +++ b/src/webassembly/async.md @@ -1,78 +1,36 @@ # Async -Rust methods in WebAssembly can be declared async. Once called, they will be scheduled on the browser's event loop. -An event handler can for instance be implemented with a tokio channel. - -Instead of `tokio::spawn`, `wasm_bindgen` provides `wasm_bindgen_futures::spawn_local`. - -Let's create a class that waits for messages on a channel to rotate an HTML element: +Rust methods in WebAssembly can be declared async. ```rust use wasm_bindgen::prelude::*; -use wasm_bindgen_futures::spawn_local; -use tokio::sync::mpsc::{channel, Sender}; - -#[derive(Debug)] -enum RotateSide { - Left, - Right, -} - -#[wasm_bindgen] -pub struct Rotator { - sender: Sender, -} +use wasm_bindgen_futures::JsFuture; +use web_sys::Response; #[wasm_bindgen] -impl Rotator { - #[wasm_bindgen(constructor)] - pub fn new(element: web_sys::HtmlElement) -> Rotator { - let (sender, mut receiver) = channel::(1); - spawn_local(async move { - let mut rotation = 0; - while let Some(rotate_side) = receiver.recv().await { - match rotate_side { - RotateSide::Left => rotation -= 45, - RotateSide::Right => rotation += 45, - } - element.set_inner_html(&rotation.to_string()); - let style = element.style(); - style - .set_property("transform", &format!("rotate({rotation}deg)")) - .expect("Failed to rotate"); - } - }); - Rotator { sender } - } - - #[wasm_bindgen] - pub async fn rotate(&self, msg: String) -> Result<(), JsValue> { - let rotate_side = match msg.as_str() { - "ArrowLeft" => RotateSide::Left, - "ArrowRight" => RotateSide::Right, - _ => return Ok(()), - }; - self.sender - .send(rotate_side) - .await - .map_err(|e| JsValue::from_str(&format!("Receiver dropped {:?}", e))) - } +pub async fn get_current_page() -> Result { + let window = web_sys::window().unwrap(); + let resp_value = JsFuture::from(window.fetch_with_str("")).await?; + let resp: Response = resp_value.dyn_into().unwrap(); + let text = JsFuture::from(resp.text()?).await?; + Ok(text) } ``` -Let's call it from Javascript - ```javascript -import init, {Rotator} from '/wasm/project.js'; +import init, { get_current_page} from '/wasm/project.js'; (async () => { - // Run the init method to initiate the WebAssembly module. await init(); - const wasmoutput = document.querySelector('#wasmoutput'); - const rotator = new Rotator(wasmoutput); - document.body.addEventListener('keydown', async (e) => { - await rotator.rotate(e.key); - }); + console.log(await get_current_page()); })(); ``` + +
+ +- Async methods are scheduled on the Javascript event loop. +- Instead of `tokio::spawn`, `wasm_bindgen` provides `wasm_bindgen_futures::spawn_local`. +- We use `JsFuture::from` to convert Javascript futures to Rust futures that we can `.await`. + +
\ No newline at end of file diff --git a/src/webassembly/error-handling.md b/src/webassembly/error-handling.md new file mode 100644 index 000000000000..de4bbcff52a8 --- /dev/null +++ b/src/webassembly/error-handling.md @@ -0,0 +1,7 @@ +# Error handling + +In this chapter we cover error handling both on the Rust side for imported Javascript methods +and on the Javascript side for imported Rust methods. + +- [Error handling for imported methods](error-handling/imported-methods.md) +- [Error handling for exported methods](error-handling/exported-methods.md) diff --git a/src/webassembly/expose-method/error-handling.md b/src/webassembly/error-handling/exported-methods.md similarity index 92% rename from src/webassembly/expose-method/error-handling.md rename to src/webassembly/error-handling/exported-methods.md index 7014ff5ccbbf..625e1a0a7602 100644 --- a/src/webassembly/expose-method/error-handling.md +++ b/src/webassembly/error-handling/exported-methods.md @@ -24,13 +24,12 @@ pub fn str_to_int(s: &str) -> Option { Javascript, click on the wasm output box to parse the string: ```javascript -import init, {set_panic_hook, str_to_int} from '/wasm/project.js'; +import init, {str_to_int} from '/wasm/project.js'; (async () => { // Run the init method to initiate the WebAssembly module. await init(); - set_panic_hook(); const wasmoutput = document.querySelector('#wasmoutput'); const input = document.createElement('input'); input.type = 'text'; @@ -51,4 +50,4 @@ import init, {set_panic_hook, str_to_int} from '/wasm/project.js'; * Click on the wasm output box to see the output * `?` and other error handling tools are also supported - \ No newline at end of file + diff --git a/src/webassembly/import-method/error-handling.md b/src/webassembly/error-handling/imported-methods.md similarity index 100% rename from src/webassembly/import-method/error-handling.md rename to src/webassembly/error-handling/imported-methods.md diff --git a/src/webassembly/expose-method.md b/src/webassembly/expose-method.md index aaa6b434dfab..5a99b06f25a2 100644 --- a/src/webassembly/expose-method.md +++ b/src/webassembly/expose-method.md @@ -24,6 +24,6 @@ pub fn add(a: i32, b: i32) -> i32 {
-* `set_panic_hook` is a convenient setup method that adds debug information to stack traces when a Wasm module panics. Don't use it in prod builds because it is rather +* `set_panic_hook` is a convenient setup method that adds debug information to stack traces when a Wasm module panics. Don't use it in prod builds because it tends to bloat the bundle size. -
\ No newline at end of file + diff --git a/src/webassembly/expose-rust-type.md b/src/webassembly/expose-rust-type.md index 58e40291a8f5..b27c68c2ed87 100644 --- a/src/webassembly/expose-rust-type.md +++ b/src/webassembly/expose-rust-type.md @@ -2,40 +2,26 @@ Similarily to methods, types can be exposed from Rust to Javascript with the `#[wasm_bindgen]` macro. -Members that implement `Copy` can be public and directly accessed from Javascript. - ```rust use wasm_bindgen::prelude::*; - #[wasm_bindgen] pub struct Counter { name: String, pub count: u8, } -``` - -Methods can also be exported - -```rust #[wasm_bindgen] impl Counter { - // Constructor will be called in JS when using `new Counter(name, count)` #[wasm_bindgen(constructor)] pub fn new(name: String, count: u8) -> Counter { Counter { name, count } } - pub fn increment(&mut self) { self.count += 1; } - - // Getter for the name #[wasm_bindgen(getter)] pub fn name(&self) -> String { self.name.clone() } - - // Setter for the name #[wasm_bindgen(setter)] pub fn set_name(&mut self, name: String) { self.name = name; @@ -43,23 +29,18 @@ impl Counter { } ``` -Add this button to the HTML file - -```html - -``` - -Javascript to use the `Counter` +Javascript to use the `Counter`. ```javascript -import init, { set_panic_hook, Counter } from "/wasm/project.js"; +import init, { Counter } from "/wasm/project.js"; (async () => { // Run the init method to initiate the WebAssembly module. await init(); - set_panic_hook(); const wasmOutput = document.querySelector("#wasmoutput"); - const button = document.querySelector("#button"); + const button = document.createElement("button"); + button.textContent = "increment"; + document.body.appendChild(button); const counter = new Counter("ButtonCounter", 42); wasmOutput.textContent = counter.count; button.addEventListener("click", () => { @@ -75,5 +56,5 @@ import init, { set_panic_hook, Counter } from "/wasm/project.js"; - `pub` members must implement copy - Type parameters and lifetime annotations are not supported yet - +- Members that implement `Copy` can be public and directly accessed from Javascript. diff --git a/src/webassembly/import-js-type.md b/src/webassembly/import-js-type.md index 9187259d9f69..4a98ae794dd4 100644 --- a/src/webassembly/import-js-type.md +++ b/src/webassembly/import-js-type.md @@ -1,69 +1,68 @@ # Import user-defined Javascript types -User-defined Javascript types can be imported by declaring the relevant methods as `extern "C"` just like +User-defined Javascript types can be imported by declaring the relevant methods as `extern "C"` just like other foreign functions. -For instance, let's declare a class `OutputBox` - ```javascript -import init, {set_panic_hook, edit_box} from '/wasm/project.js'; - -class OutputBox { - constructor(element) { - this.element = element; - this.lastText = null; - } - - setText(text) { - this.element.innerHTML = text; - } - - get currentText() { - return this.element.innerHTML; - } -} - -window.OutputBox = OutputBox; - -(async () => { - // Run the init method to initiate the WebAssembly module. - await init(); - set_panic_hook(); - const wasmoutput = document.querySelector('#wasmoutput'); - const outputBox = new OutputBox(wasmoutput); - const input = document.createElement('input'); - document.body.appendChild(input); - wasmoutput.onclick = () => { - const inputValue = input.value; - edit_box(outputBox, inputValue); - }; -})(); +import init, { edit_box } from "/wasm/project.js"; + +window.OutputBox = class { + constructor(element) { + this.element = element; + this.lastText = null; + } + setText(text) { + this.element.innerHTML = text; + } + get currentText() { + return this.element.innerHTML; + } +}; ``` It can be imported as such in Rust ```rust +use wasm_bindgen::prelude::*; #[wasm_bindgen] extern "C" { pub type OutputBox; - #[wasm_bindgen(constructor)] - pub fn new(text: i32) -> OutputBox; - + pub fn new(element: web_sys::HtmlElement) -> OutputBox; #[wasm_bindgen(method)] pub fn setText(this: &OutputBox, text: &str); - - // Has to return owned #[wasm_bindgen(method, getter)] pub fn lastText(this: &OutputBox) -> Option; - #[wasm_bindgen(method, setter)] pub fn set_lastText(this: &OutputBox, text: Option); - #[wasm_bindgen(method, getter)] pub fn currentText(this: &OutputBox) -> String; } +``` + +
+ +- Getters and Setters have to be declared with an added parameter in the proc macro. +- `null` and `undefined` can be both represented by `Option::None` + +Try it in action: + +```javascript +(async () => { + // Run the init method to initiate the WebAssembly module. + await init(); + const wasmoutput = document.querySelector("#wasmoutput"); + const outputBox = new OutputBox(wasmoutput); + const input = document.createElement("input"); + document.body.appendChild(input); + wasmoutput.onclick = () => { + const inputValue = input.value; + edit_box(outputBox, inputValue); + }; +})(); +``` +```rust #[wasm_bindgen] pub fn edit_box(output_box: &OutputBox, text: &str) { match text { @@ -81,9 +80,4 @@ pub fn edit_box(output_box: &OutputBox, text: &str) { } ``` -
- -* Getters and Setters have to be declared with an added parameter in the proc macro. -* `null` and `undefined` can be both represented by `Option::None` - -
\ No newline at end of file +
diff --git a/src/webassembly/import-method.md b/src/webassembly/import-method.md index 5a455df50e4e..b507e17460e2 100644 --- a/src/webassembly/import-method.md +++ b/src/webassembly/import-method.md @@ -1,19 +1,13 @@ # Import a Javascript method -Since Wasm runs in the browser, we will want to interact directly with Javascript APIs from Rust. -For instance `println!` will not log to the javascript console, so we need to use `console.log`. -Similarly, we want to be able to call `alert`. This works the same way as FFIs with C. +Methods from javascript can be imported directly as `extern "C"` bindings. ```rust #[wasm_bindgen] extern "C" { fn alert(s: &str); - - // `js_namespace` will get values inside of a nested object in window. Here, `window.console.log` #[wasm_bindgen(js_namespace = console)] pub fn log(s: &str); - - // jsMethod is a user defined method defined in the `window` object pub fn jsMethod(); } @@ -45,4 +39,8 @@ window.jsMethod = jsMethod;
+Since Wasm runs in the browser, we will want to interact directly with Javascript APIs from Rust. +For instance `println!` will not log to the javascript console, so we need to use `console.log`. +Similarly, we want to be able to call `alert`. This works the same way as FFIs with C. +
\ No newline at end of file diff --git a/src/webassembly/import-method/web-sys.md b/src/webassembly/import-method/web-sys.md index 24e7feebbf69..a04c63aaec20 100644 --- a/src/webassembly/import-method/web-sys.md +++ b/src/webassembly/import-method/web-sys.md @@ -22,18 +22,16 @@ pub fn add_a_cat() -> Result<(), JsValue> { ``` ```javascript -import init, {set_panic_hook, add_a_cat} from '/wasm/project.js'; - - -(async () => { - // Run the init method to initiate the WebAssembly module. - await init(); - set_panic_hook(); - const button = document.createElement("button"); - button.textContent = "Add a cat"; - document.body.appendChild(button); - button.addEventListener("click", () => { - add_a_cat(); - }); +import init, { add_a_cat } from "/wasm/project.js"; + +(async () => { + // Run the init method to initiate the WebAssembly module. + await init(); + const button = document.createElement("button"); + button.textContent = "Add a cat"; + document.body.appendChild(button); + button.addEventListener("click", () => { + add_a_cat(); + }); })(); ``` diff --git a/src/webassembly/limitations/borrow-checker.md b/src/webassembly/limitations/borrow-checker.md index b8503706b5c1..eb9cd4eb21c7 100644 --- a/src/webassembly/limitations/borrow-checker.md +++ b/src/webassembly/limitations/borrow-checker.md @@ -1,60 +1,40 @@ # Borrow Checker -When we export a Rust type to Javascript and the pass an instance of this type to a method that takes ownership of it, the javascript variable will be cleared and dereferencing it will throw a runtime error. -This essentially implements the borrow checker at Runtime in Javascript. +When we export a Rust type to Javascript we need to beware about the borrow checker on the Javascript side. ```rust #[wasm_bindgen] -pub struct MultiCounter { - // We use the counter from the previous slide - counters: Vec, -} - +pub struct RustType {} #[wasm_bindgen] -impl MultiCounter { - #[wasm_bindgen(constructor)] - pub fn new() -> MultiCounter { - MultiCounter { counters: Vec::new() } - } - - pub fn increment(&mut self) { - for counter in &mut self.counters { - counter.increment(); - } - } - - pub fn add_counter(&mut self, counter: Counter) { - self.counter.push(counter); +impl RustType { + #[wasm_bindgen] + pub fn new() -> RustType { + RustType {} } } +#[wasm_bindgen] +pub fn takes_struct(s: RustType) -> RustType { + s +} ``` ```javascript -import init, {set_panic_hook, Counter, MultiCounter} from '/wasm/project.js'; +import init, {RustType, takes_struct} from '/wasm/project.js'; (async () => { // Run the init method to initiate the WebAssembly module. await init(); - set_panic_hook(); - const wasmOutput = document.querySelector("#wasmoutput"); - const button = document.querySelector("#button"); - - const counter = new Counter("ButtonCounter", 42); - counter.increment(); - // Works fine - wasmOutput.textContent = counter.count; - - const multiCounter = new MultiCounter(); - // Move counter into the MultiCounter - multiCounter.add_counter(counter); - // Error: Open console - counter.increment(); + const rustType = RustType.new(); + const moved = takes_struct(rustType); + console.log(moved); + console.log(rustType); })(); ```
-* `counter` is moved before the second call, so the code panics -* Ownership rules must be respected +* `rustType` is moved so it points to null in the second log. +* Ownership rules must be respected even in Javascript. +* Integral types in JS that are automatically translated to Rust do not have those constraints. `(String, Vec, etc.)`
\ No newline at end of file diff --git a/src/webassembly/limitations/closures.md b/src/webassembly/limitations/closures.md index a236620292fa..b303910f5598 100644 --- a/src/webassembly/limitations/closures.md +++ b/src/webassembly/limitations/closures.md @@ -1,20 +1,17 @@ # Closures -Closures can be returned to Rust and executed on the Wasm runtime. +Closures created in Rust have to be returned to so they won't be dropped. ```rust use wasm_bindgen::prelude::*; - #[wasm_bindgen] extern "C" { fn setInterval(closure: &Closure, millis: u32) -> f64; } - #[wasm_bindgen] pub struct ClosureHandle { closure: Closure, } - #[wasm_bindgen] pub fn timeout_set_seconds(elem: web_sys::HtmlElement) -> ClosureHandle { let seconds = Rc::new(RefCell::new(0usize)); @@ -30,24 +27,23 @@ pub fn timeout_set_seconds(elem: web_sys::HtmlElement) -> ClosureHandle { ``` ```javascript -import init, {set_panic_hook, timeout_set_seconds} from '/wasm/project.js'; +import init, { timeout_set_seconds } from "/wasm/project.js"; -(async () => { - // Run the init method to initiate the WebAssembly module. - await init(); - const wasmOutput = document.querySelector("#wasmoutput"); - timeout_set_seconds(wasmOutput); +(async () => { + // Run the init method to initiate the WebAssembly module. + await init(); + const wasmOutput = document.querySelector("#wasmoutput"); + timeout_set_seconds(wasmOutput); })(); - ```
-* Since the function that creates the closure keeps its ownership, the closure would be dropped if we did't return it. - * Returning ownership allows the JS runtime to manage the lifetime of the closure and to collect it when it can. - * Try returning nothing from the method. -* Closures can only be passed by reference to Wasm functions. - * This is why we pass `&Closure` to `setInterval`. - * This is also why we need to create `ClosureHandle` to return the closure. +- Since the function that creates the closure keeps its ownership, the closure would be dropped if we did't return it. + - Returning ownership allows the JS runtime to manage the lifetime of the closure and to collect it when it can. + - Try returning nothing from the method. +- Closures can only be passed by reference to Wasm functions. + - This is why we pass `&Closure` to `setInterval`. + - This is also why we need to create `ClosureHandle` to return the closure. -
\ No newline at end of file +