Skip to content

Commit

Permalink
Minimal repro
Browse files Browse the repository at this point in the history
  • Loading branch information
jthemphill committed Sep 14, 2023
1 parent 06cf032 commit b276cd6
Show file tree
Hide file tree
Showing 11 changed files with 286 additions and 0 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ members = [
"examples/performance",
"examples/raytrace-parallel",
"examples/request-animation-frame",
"examples/thread-object-sharing",
"examples/todomvc",
"examples/wasm-audio-worklet",
"examples/wasm-in-wasm",
Expand Down
12 changes: 12 additions & 0 deletions examples/thread-object-sharing/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
[package]
name = "thread-object-sharing"
version = "0.1.0"
authors = ["The wasm-bindgen Developers"]
edition = "2021"
rust-version = "1.74"

[lib]
crate-type = ["cdylib"]

[dependencies]
wasm-bindgen = "0.2.87"
9 changes: 9 additions & 0 deletions examples/thread-object-sharing/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Object sharing across threads

You can build the example locally with:

```
$ python3 ./run.py
```

and then visiting http://localhost:8080 in a browser should run the example!
58 changes: 58 additions & 0 deletions examples/thread-object-sharing/build.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
#!/usr/bin/env python3
import os
import subprocess

root_dir = os.path.dirname(__file__)

# A couple of steps are necessary to get this build working which makes it slightly
# nonstandard compared to most other builds.
#
# * First, the Rust standard library needs to be recompiled with atomics
# enabled. to do that we use Cargo's unstable `-Zbuild-std` feature.
#
# * Next we need to compile everything with the `atomics` and `bulk-memory`
# features enabled, ensuring that LLVM will generate atomic instructions,
# shared memory, passive segments, etc.

os.environ.update(
{"RUSTFLAGS": "-C target-feature=+atomics,+bulk-memory,+mutable-globals"}
)

subprocess.run(
[
"cargo",
"build",
"--target",
"wasm32-unknown-unknown",
"--release",
"-Zbuild-std=std,panic_abort",
],
cwd=root_dir,
).check_returncode()

# Note the usage of `--target no-modules` here which is required for passing
# the memory import to each wasm module.
subprocess.run(
[
"cargo",
"run",
"-p",
"wasm-bindgen-cli",
"--",
os.path.join(
root_dir,
"..",
"..",
"target",
"wasm32-unknown-unknown",
"release",
"thread_object_sharing.wasm",
),
"--out-dir",
os.path.join(root_dir, "pkg"),
"--target",
"web",
# "--split-linked-modules",
],
cwd=root_dir,
).check_returncode()
5 changes: 5 additions & 0 deletions examples/thread-object-sharing/build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#!/bin/sh

set -ex

python3 build.py
20 changes: 20 additions & 0 deletions examples/thread-object-sharing/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<!DOCTYPE html>
<html lang="en">

<head>
<title>Sharing a wasm object across workers</title>
<link rel="icon" href="data:image/x-icon;base64,AA">
<script type="module" src="index.js"></script>
</head>

<body>
<button id="toggle-button" disabled>Loading...</button>
<div id="value-display">
<meter id="value-meter" min="-1000" max="1000">Some atomic value</meter>
<span id="value-display-text">0</span>
</div>
<input id="increment-input" type="range" min="-10" max="10" value="0" />
<label for="increment-input">Amount to increment per tick</label>
</body>

</html>
78 changes: 78 additions & 0 deletions examples/thread-object-sharing/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/** @type {import('./pkg/thread_object_sharing')} */
import initWasm, { OpaqueObject } from "./pkg/thread_object_sharing.js";

// Create a few webworkers, leaving some breathing room so our computer doesn't die
const numWorkers = navigator.hardwareConcurrency / 2;
const workers = [];
for (let i = 0; i < numWorkers; ++i) {
workers.push(new Worker(
new URL('./worker.js', import.meta.url),
{ name: 'Call OpaqueObject.get() a bunch of times', type: 'module' },
));
}

// Load the webassembly and send our memory to the workers
const wasmInternals = await initWasm();
for (const worker of workers) {
worker.postMessage({ type: 'load', memory: wasmInternals.memory });
}

const opaqueObject = new OpaqueObject(47);

// True iff webworkers are running
let running = false;

// Visual representation of opaqueObject.get()
const valueMeter = window.document.getElementById('value-meter');
const valueDisplayText = window.document.getElementById('value-display-text');

// Change the rate at which the webworkers increment opaqueObject.get()'s value
const incrementInput = window.document.getElementById('increment-input');
incrementInput.onchange = (event) => {
const incrementVal = event.target.value;
for (const worker of workers) {
worker.postMessage({ type: 'increment', incrementVal });
}
}

/**
* Start the webworkers
*/
function start() {
for (const worker of workers) {
worker.postMessage({ type: 'start', opaqueObjectPtr: opaqueObject.__wbg_ptr });
}
running = true;
}

/**
* Stop the webworkers
*/
function stop() {
for (const worker of workers) {
worker.postMessage({ type: 'stop' });
}
running = false;
}

// Clickable button to start and stop the webworkers
const toggleButton = window.document.getElementById('toggle-button');
toggleButton.onclick = () => {
if (running) {
stop();
} else {
start();
}
toggleButton.innerText = running ? 'stop' : 'start';
};
toggleButton.removeAttribute('disabled');
toggleButton.innerText = running ? 'stop' : 'start';

function render() {
const val = opaqueObject.get();
valueMeter.setAttribute('value', val);
valueDisplayText.innerText = val.toString();

window.requestAnimationFrame(render);
}
window.requestAnimationFrame(render);
9 changes: 9 additions & 0 deletions examples/thread-object-sharing/run.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#!/usr/bin/env python3
import os
import subprocess

root_dir = os.path.dirname(__file__)

subprocess.run(["python3", "build.py"], cwd=root_dir).check_returncode()

subprocess.run(["python3", "server.py"], cwd=root_dir).check_returncode()
16 changes: 16 additions & 0 deletions examples/thread-object-sharing/server.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#!/usr/bin/env python3
from http.server import HTTPServer, SimpleHTTPRequestHandler, test
import sys


class RequestHandler(SimpleHTTPRequestHandler):
def end_headers(self):
self.send_header("Cross-Origin-Opener-Policy", "same-origin")
self.send_header("Cross-Origin-Embedder-Policy", "require-corp")
SimpleHTTPRequestHandler.end_headers(self)


if __name__ == "__main__":
test(
RequestHandler, HTTPServer, port=int(sys.argv[1]) if len(sys.argv) > 1 else 8080
)
28 changes: 28 additions & 0 deletions examples/thread-object-sharing/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
use std::sync::atomic::{AtomicI32, Ordering};

use wasm_bindgen::prelude::*;

#[wasm_bindgen]
pub struct OpaqueObject {
val: AtomicI32,
}

#[wasm_bindgen]
impl OpaqueObject {
#[wasm_bindgen(constructor)]
pub fn new(val: i32) -> Self {
Self {
val: AtomicI32::new(val),
}
}

#[wasm_bindgen]
pub fn get(&self) -> i32 {
self.val.load(Ordering::SeqCst)
}

#[wasm_bindgen]
pub fn add(&self, num: i32) -> i32 {
self.val.fetch_add(num, Ordering::SeqCst)
}
}
50 changes: 50 additions & 0 deletions examples/thread-object-sharing/worker.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/** @type {import('./pkg/thread_object_sharing')} */
import init, { OpaqueObject } from "./pkg/thread_object_sharing.js";

/**
* @typedef {{type: 'load', memory: WebAssembly.Memory}} WorkerRequestLoad
* @typedef {{type: 'start', opaqueObjectPtr: number}} WorkerRequestStart
* @typedef {{type: 'stop'}} WorkerRequestStop
* @typedef {{type: 'increment', 'incrementVal': number}} WorkerRequestIncrement
* @typedef {WorkerRequestLoad | WorkerRequestStart | WorkerRequestStop | WorkerRequestIncrement} WorkerRequest
*/

/** @type {number | undefined} */
let intervalTimer = undefined;
let incrementVal = 10;

/** @type {OpaqueObject} */
const opaqueObject = Object.create(OpaqueObject.prototype);

function tick() {
opaqueObject.add(incrementVal);
}

self.onmessage = async (event) => {
/**
* @type {WorkerRequest}
*/
const request = event.data;

switch (request.type) {
case 'load':
await init(undefined, request.memory);
break;
case 'start':
opaqueObject.__wbg_ptr = request.opaqueObjectPtr;
if (intervalTimer === undefined) {
intervalTimer = self.setInterval(tick, 15);
console.log('starting intervalTimer');
}
break;
case 'stop':
console.log('stopping intervalTimer');
self.clearInterval(intervalTimer);
intervalTimer = undefined;
break;
case 'increment':
incrementVal = request.incrementVal;
console.log(`New increment value: ${incrementVal}`);
break;
}
};

0 comments on commit b276cd6

Please sign in to comment.