Skip to content

Commit

Permalink
More concise class
Browse files Browse the repository at this point in the history
  • Loading branch information
sakex committed Aug 17, 2023
1 parent d7579d7 commit 7dd1676
Show file tree
Hide file tree
Showing 18 changed files with 187 additions and 245 deletions.
4 changes: 2 additions & 2 deletions book.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
9 changes: 7 additions & 2 deletions src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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)
12 changes: 12 additions & 0 deletions src/exercises/webassembly/webassembly.md
Original file line number Diff line number Diff line change
@@ -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)
5 changes: 3 additions & 2 deletions src/rust-wasm-template/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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',
]
32 changes: 11 additions & 21 deletions src/rust-wasm-template/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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<String>;
#[wasm_bindgen(method, setter)]
pub fn set_lastText(this: &OutputBox, text: Option<String>);
#[wasm_bindgen(method, getter)]
pub fn currentText(this: &OutputBox) -> String;
}
24 changes: 24 additions & 0 deletions src/rust-wasm-template/static/exercises/game_of_life/index.mjs
Original file line number Diff line number Diff line change
@@ -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';
});
})();
5 changes: 2 additions & 3 deletions src/rust-wasm-template/static/index.mjs
Original file line number Diff line number Diff line change
@@ -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());
})();
80 changes: 19 additions & 61 deletions src/webassembly/async.md
Original file line number Diff line number Diff line change
@@ -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<RotateSide>,
}
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::<RotateSide>(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<JsValue, JsValue> {
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());
})();

```

<details>

- 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`.

</details>
7 changes: 7 additions & 0 deletions src/webassembly/error-handling.md
Original file line number Diff line number Diff line change
@@ -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)
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,12 @@ pub fn str_to_int(s: &str) -> Option<i32> {
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';
Expand All @@ -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

</details>
</details>
4 changes: 2 additions & 2 deletions src/webassembly/expose-method.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,6 @@ pub fn add(a: i32, b: i32) -> i32 {

<details>

* `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.

</details>
</details>
31 changes: 6 additions & 25 deletions src/webassembly/expose-rust-type.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,64 +2,45 @@

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;
}
}
```

Add this button to the HTML file

```html
<button id="button">Increment</button>
```

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", () => {
Expand All @@ -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.
</details>
Loading

0 comments on commit 7dd1676

Please sign in to comment.