Add WASM support for executor
* Adds an executor for WASM runtimes based on wasm_bindgen. * Add time driver based on JS time handling. * Add example that can run in browser locally. * Update to critical-section version that supports 'std' flag
This commit is contained in:
parent
f1c35b40c7
commit
e24528051b
2
.github/workflows/rust.yml
vendored
2
.github/workflows/rust.yml
vendored
@ -94,6 +94,8 @@ jobs:
|
|||||||
target: thumbv6m-none-eabi
|
target: thumbv6m-none-eabi
|
||||||
- package: examples/stm32g0
|
- package: examples/stm32g0
|
||||||
target: thumbv6m-none-eabi
|
target: thumbv6m-none-eabi
|
||||||
|
- package: examples/wasm
|
||||||
|
target: wasm32-unknown-unknown
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
with:
|
with:
|
||||||
|
@ -18,3 +18,4 @@ nrf = []
|
|||||||
stm32 = []
|
stm32 = []
|
||||||
rp = []
|
rp = []
|
||||||
std = []
|
std = []
|
||||||
|
wasm = []
|
||||||
|
@ -450,3 +450,82 @@ pub fn main(args: TokenStream, item: TokenStream) -> TokenStream {
|
|||||||
};
|
};
|
||||||
result.into()
|
result.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "wasm")]
|
||||||
|
#[proc_macro_attribute]
|
||||||
|
pub fn main(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||||
|
let macro_args = syn::parse_macro_input!(args as syn::AttributeArgs);
|
||||||
|
let task_fn = syn::parse_macro_input!(item as syn::ItemFn);
|
||||||
|
|
||||||
|
let macro_args = match MainArgs::from_list(¯o_args) {
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(e) => {
|
||||||
|
return TokenStream::from(e.write_errors());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let embassy_path = macro_args.embassy_prefix.append("embassy");
|
||||||
|
|
||||||
|
let mut fail = false;
|
||||||
|
if task_fn.sig.asyncness.is_none() {
|
||||||
|
task_fn
|
||||||
|
.sig
|
||||||
|
.span()
|
||||||
|
.unwrap()
|
||||||
|
.error("task functions must be async")
|
||||||
|
.emit();
|
||||||
|
fail = true;
|
||||||
|
}
|
||||||
|
if !task_fn.sig.generics.params.is_empty() {
|
||||||
|
task_fn
|
||||||
|
.sig
|
||||||
|
.span()
|
||||||
|
.unwrap()
|
||||||
|
.error("main function must not be generic")
|
||||||
|
.emit();
|
||||||
|
fail = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
let args = task_fn.sig.inputs.clone();
|
||||||
|
|
||||||
|
if args.len() != 1 {
|
||||||
|
task_fn
|
||||||
|
.sig
|
||||||
|
.span()
|
||||||
|
.unwrap()
|
||||||
|
.error("main function must have one argument")
|
||||||
|
.emit();
|
||||||
|
fail = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if fail {
|
||||||
|
return TokenStream::new();
|
||||||
|
}
|
||||||
|
|
||||||
|
let task_fn_body = task_fn.block.clone();
|
||||||
|
|
||||||
|
let embassy_path = embassy_path.path();
|
||||||
|
let embassy_prefix_lit = macro_args.embassy_prefix.literal();
|
||||||
|
|
||||||
|
let result = quote! {
|
||||||
|
#[#embassy_path::task(embassy_prefix = #embassy_prefix_lit)]
|
||||||
|
async fn __embassy_main(#args) {
|
||||||
|
#task_fn_body
|
||||||
|
}
|
||||||
|
|
||||||
|
use wasm_bindgen::prelude::*;
|
||||||
|
|
||||||
|
#[wasm_bindgen(start)]
|
||||||
|
pub fn main() -> Result<(), JsValue> {
|
||||||
|
static EXECUTOR: #embassy_path::util::Forever<#embassy_path::executor::Executor> = #embassy_path::util::Forever::new();
|
||||||
|
let executor = EXECUTOR.put(#embassy_path::executor::Executor::new());
|
||||||
|
|
||||||
|
executor.start(|spawner| {
|
||||||
|
spawner.spawn(__embassy_main(spawner)).unwrap();
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
};
|
||||||
|
result.into()
|
||||||
|
}
|
||||||
|
@ -45,7 +45,7 @@ cortex-m = "0.7.3"
|
|||||||
embedded-hal = "0.2.6"
|
embedded-hal = "0.2.6"
|
||||||
embedded-dma = "0.1.2"
|
embedded-dma = "0.1.2"
|
||||||
futures = { version = "0.3.17", default-features = false }
|
futures = { version = "0.3.17", default-features = false }
|
||||||
critical-section = "0.2.1"
|
critical-section = "0.2.2"
|
||||||
rand_core = "0.6.3"
|
rand_core = "0.6.3"
|
||||||
|
|
||||||
nrf52805-pac = { version = "0.10.1", optional = true, features = [ "rt" ] }
|
nrf52805-pac = { version = "0.10.1", optional = true, features = [ "rt" ] }
|
||||||
|
@ -27,7 +27,7 @@ defmt = { version = "0.2.3", optional = true }
|
|||||||
log = { version = "0.4.14", optional = true }
|
log = { version = "0.4.14", optional = true }
|
||||||
cortex-m-rt = ">=0.6.15,<0.8"
|
cortex-m-rt = ">=0.6.15,<0.8"
|
||||||
cortex-m = "0.7.3"
|
cortex-m = "0.7.3"
|
||||||
critical-section = "0.2.1"
|
critical-section = "0.2.2"
|
||||||
|
|
||||||
rp2040-pac2 = { git = "https://github.com/embassy-rs/rp2040-pac2", rev="9ad7223a48a065e612bc7dc7be5bf5bd0b41cfc4", features = ["rt"] }
|
rp2040-pac2 = { git = "https://github.com/embassy-rs/rp2040-pac2", rev="9ad7223a48a065e612bc7dc7be5bf5bd0b41cfc4", features = ["rt"] }
|
||||||
#rp2040-pac2 = { path = "../../rp/rp2040-pac2", features = ["rt"] }
|
#rp2040-pac2 = { path = "../../rp/rp2040-pac2", features = ["rt"] }
|
||||||
|
@ -21,7 +21,7 @@ futures = { version = "0.3.17", default-features = false, features = ["async-awa
|
|||||||
rand_core = "0.6.3"
|
rand_core = "0.6.3"
|
||||||
sdio-host = "0.5.0"
|
sdio-host = "0.5.0"
|
||||||
embedded-sdmmc = { git = "https://github.com/thalesfragoso/embedded-sdmmc-rs", branch = "async", optional = true }
|
embedded-sdmmc = { git = "https://github.com/thalesfragoso/embedded-sdmmc-rs", branch = "async", optional = true }
|
||||||
critical-section = "0.2.1"
|
critical-section = "0.2.2"
|
||||||
bare-metal = "1.0.0"
|
bare-metal = "1.0.0"
|
||||||
atomic-polyfill = "0.1.3"
|
atomic-polyfill = "0.1.3"
|
||||||
stm32-metapac = { version = "0.1.0", path = "../stm32-metapac", features = ["rt"] }
|
stm32-metapac = { version = "0.1.0", path = "../stm32-metapac", features = ["rt"] }
|
||||||
|
@ -8,6 +8,7 @@ resolver = "2"
|
|||||||
[features]
|
[features]
|
||||||
default = []
|
default = []
|
||||||
std = ["futures/std", "embassy-traits/std", "time", "time-tick-1mhz", "embassy-macros/std"]
|
std = ["futures/std", "embassy-traits/std", "time", "time-tick-1mhz", "embassy-macros/std"]
|
||||||
|
wasm = ["wasm-bindgen", "js-sys", "embassy-macros/wasm", "wasm-timer", "time", "time-tick-1mhz"]
|
||||||
|
|
||||||
# Enable `embassy::time` module.
|
# Enable `embassy::time` module.
|
||||||
# NOTE: This feature is only intended to be enabled by crates providing the time driver implementation.
|
# NOTE: This feature is only intended to be enabled by crates providing the time driver implementation.
|
||||||
@ -40,10 +41,15 @@ pin-project = { version = "1.0.8", default-features = false }
|
|||||||
embassy-macros = { version = "0.1.0", path = "../embassy-macros"}
|
embassy-macros = { version = "0.1.0", path = "../embassy-macros"}
|
||||||
embassy-traits = { version = "0.1.0", path = "../embassy-traits"}
|
embassy-traits = { version = "0.1.0", path = "../embassy-traits"}
|
||||||
atomic-polyfill = "0.1.3"
|
atomic-polyfill = "0.1.3"
|
||||||
critical-section = "0.2.1"
|
critical-section = "0.2.2"
|
||||||
embedded-hal = "0.2.6"
|
embedded-hal = "0.2.6"
|
||||||
heapless = "0.7.5"
|
heapless = "0.7.5"
|
||||||
|
|
||||||
|
# WASM dependencies
|
||||||
|
wasm-bindgen = { version = "0.2.76", features = ["nightly"], optional = true }
|
||||||
|
js-sys = { version = "0.3", optional = true }
|
||||||
|
wasm-timer = { version = "0.2.5", optional = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
embassy = { path = ".", features = ["executor-agnostic"] }
|
embassy = { path = ".", features = ["executor-agnostic"] }
|
||||||
futures-executor = { version = "0.3.17", features = [ "thread-pool" ] }
|
futures-executor = { version = "0.3.17", features = [ "thread-pool" ] }
|
||||||
|
74
embassy/src/executor/arch/wasm.rs
Normal file
74
embassy/src/executor/arch/wasm.rs
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
use core::marker::PhantomData;
|
||||||
|
use js_sys::Promise;
|
||||||
|
use wasm_bindgen::prelude::*;
|
||||||
|
|
||||||
|
use super::{
|
||||||
|
raw::{self, util::UninitCell},
|
||||||
|
Spawner,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// WASM executor, wasm_bindgen to schedule tasks on the JS event loop.
|
||||||
|
pub struct Executor {
|
||||||
|
inner: raw::Executor,
|
||||||
|
ctx: &'static WasmContext,
|
||||||
|
not_send: PhantomData<*mut ()>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) struct WasmContext {
|
||||||
|
promise: Promise,
|
||||||
|
closure: UninitCell<Closure<dyn FnMut(JsValue)>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WasmContext {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
promise: Promise::resolve(&JsValue::undefined()),
|
||||||
|
closure: UninitCell::uninit(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Executor {
|
||||||
|
/// Create a new Executor.
|
||||||
|
pub fn new() -> Self {
|
||||||
|
let ctx = &*Box::leak(Box::new(WasmContext::new()));
|
||||||
|
let inner = raw::Executor::new(
|
||||||
|
|p| unsafe {
|
||||||
|
let ctx = &*(p as *const () as *const WasmContext);
|
||||||
|
let _ = ctx.promise.then(ctx.closure.as_mut());
|
||||||
|
},
|
||||||
|
ctx as *const _ as _,
|
||||||
|
);
|
||||||
|
Self {
|
||||||
|
inner,
|
||||||
|
not_send: PhantomData,
|
||||||
|
ctx,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Run the executor.
|
||||||
|
///
|
||||||
|
/// The `init` closure is called with a [`Spawner`] that spawns tasks on
|
||||||
|
/// this executor. Use it to spawn the initial task(s). After `init` returns,
|
||||||
|
/// the executor starts running the tasks.
|
||||||
|
///
|
||||||
|
/// To spawn more tasks later, you may keep copies of the [`Spawner`] (it is `Copy`),
|
||||||
|
/// for example by passing it as an argument to the initial tasks.
|
||||||
|
///
|
||||||
|
/// This function requires `&'static mut self`. This means you have to store the
|
||||||
|
/// Executor instance in a place where it'll live forever and grants you mutable
|
||||||
|
/// access. There's a few ways to do this:
|
||||||
|
///
|
||||||
|
/// - a [Forever](crate::util::Forever) (safe)
|
||||||
|
/// - a `static mut` (unsafe)
|
||||||
|
/// - a local variable in a function you know never returns (like `fn main() -> !`), upgrading its lifetime with `transmute`. (unsafe)
|
||||||
|
pub fn start(&'static mut self, init: impl FnOnce(Spawner)) {
|
||||||
|
unsafe {
|
||||||
|
let executor = &self.inner;
|
||||||
|
self.ctx.closure.write(Closure::new(move |_| {
|
||||||
|
executor.poll();
|
||||||
|
}));
|
||||||
|
init(self.inner.spawner());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -3,7 +3,8 @@
|
|||||||
#![deny(missing_docs)]
|
#![deny(missing_docs)]
|
||||||
|
|
||||||
#[cfg_attr(feature = "std", path = "arch/std.rs")]
|
#[cfg_attr(feature = "std", path = "arch/std.rs")]
|
||||||
#[cfg_attr(not(feature = "std"), path = "arch/arm.rs")]
|
#[cfg_attr(feature = "wasm", path = "arch/wasm.rs")]
|
||||||
|
#[cfg_attr(not(any(feature = "std", feature = "wasm")), path = "arch/arm.rs")]
|
||||||
mod arch;
|
mod arch;
|
||||||
pub mod raw;
|
pub mod raw;
|
||||||
mod spawner;
|
mod spawner;
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
mod run_queue;
|
mod run_queue;
|
||||||
#[cfg(feature = "time")]
|
#[cfg(feature = "time")]
|
||||||
mod timer_queue;
|
mod timer_queue;
|
||||||
mod util;
|
pub(crate) mod util;
|
||||||
mod waker;
|
mod waker;
|
||||||
|
|
||||||
use atomic_polyfill::{AtomicU32, Ordering};
|
use atomic_polyfill::{AtomicU32, Ordering};
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
#![cfg_attr(not(feature = "std"), no_std)]
|
#![cfg_attr(not(any(feature = "std", feature = "wasm")), no_std)]
|
||||||
#![feature(generic_associated_types)]
|
#![feature(generic_associated_types)]
|
||||||
#![feature(const_fn_trait_bound)]
|
#![feature(const_fn_trait_bound)]
|
||||||
#![feature(const_fn_fn_ptr_basics)]
|
#![feature(const_fn_fn_ptr_basics)]
|
||||||
|
135
embassy/src/time/driver_wasm.rs
Normal file
135
embassy/src/time/driver_wasm.rs
Normal file
@ -0,0 +1,135 @@
|
|||||||
|
use atomic_polyfill::{AtomicU8, Ordering};
|
||||||
|
use std::cell::UnsafeCell;
|
||||||
|
use std::mem::MaybeUninit;
|
||||||
|
use std::ptr;
|
||||||
|
use std::sync::{Mutex, Once};
|
||||||
|
use wasm_bindgen::prelude::*;
|
||||||
|
use wasm_timer::Instant as StdInstant;
|
||||||
|
|
||||||
|
use crate::time::driver::{AlarmHandle, Driver};
|
||||||
|
|
||||||
|
const ALARM_COUNT: usize = 4;
|
||||||
|
|
||||||
|
struct AlarmState {
|
||||||
|
token: Option<f64>,
|
||||||
|
closure: Option<Closure<dyn FnMut() + 'static>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl Send for AlarmState {}
|
||||||
|
|
||||||
|
impl AlarmState {
|
||||||
|
const fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
token: None,
|
||||||
|
closure: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
extern "C" {
|
||||||
|
fn setTimeout(closure: &Closure<dyn FnMut()>, millis: u32) -> f64;
|
||||||
|
fn clearTimeout(token: f64);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct TimeDriver {
|
||||||
|
alarm_count: AtomicU8,
|
||||||
|
|
||||||
|
once: Once,
|
||||||
|
alarms: UninitCell<Mutex<[AlarmState; ALARM_COUNT]>>,
|
||||||
|
zero_instant: UninitCell<StdInstant>,
|
||||||
|
}
|
||||||
|
|
||||||
|
const ALARM_NEW: AlarmState = AlarmState::new();
|
||||||
|
crate::time_driver_impl!(static DRIVER: TimeDriver = TimeDriver {
|
||||||
|
alarm_count: AtomicU8::new(0),
|
||||||
|
once: Once::new(),
|
||||||
|
alarms: UninitCell::uninit(),
|
||||||
|
zero_instant: UninitCell::uninit(),
|
||||||
|
});
|
||||||
|
|
||||||
|
impl TimeDriver {
|
||||||
|
fn init(&self) {
|
||||||
|
self.once.call_once(|| unsafe {
|
||||||
|
self.alarms.write(Mutex::new([ALARM_NEW; ALARM_COUNT]));
|
||||||
|
self.zero_instant.write(StdInstant::now());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Driver for TimeDriver {
|
||||||
|
fn now(&self) -> u64 {
|
||||||
|
self.init();
|
||||||
|
|
||||||
|
let zero = unsafe { self.zero_instant.read() };
|
||||||
|
StdInstant::now().duration_since(zero).as_micros() as u64
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn allocate_alarm(&self) -> Option<AlarmHandle> {
|
||||||
|
let id = self
|
||||||
|
.alarm_count
|
||||||
|
.fetch_update(Ordering::AcqRel, Ordering::Acquire, |x| {
|
||||||
|
if x < ALARM_COUNT as u8 {
|
||||||
|
Some(x + 1)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
match id {
|
||||||
|
Ok(id) => Some(AlarmHandle::new(id)),
|
||||||
|
Err(_) => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_alarm_callback(&self, alarm: AlarmHandle, callback: fn(*mut ()), ctx: *mut ()) {
|
||||||
|
self.init();
|
||||||
|
let mut alarms = unsafe { self.alarms.as_ref() }.lock().unwrap();
|
||||||
|
let alarm = &mut alarms[alarm.id() as usize];
|
||||||
|
alarm.closure.replace(Closure::new(move || {
|
||||||
|
callback(ctx);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_alarm(&self, alarm: AlarmHandle, timestamp: u64) {
|
||||||
|
self.init();
|
||||||
|
let mut alarms = unsafe { self.alarms.as_ref() }.lock().unwrap();
|
||||||
|
let alarm = &mut alarms[alarm.id() as usize];
|
||||||
|
let timeout = (timestamp - self.now()) as u32;
|
||||||
|
if let Some(token) = alarm.token {
|
||||||
|
clearTimeout(token);
|
||||||
|
}
|
||||||
|
alarm.token = Some(setTimeout(alarm.closure.as_ref().unwrap(), timeout / 1000));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) struct UninitCell<T>(MaybeUninit<UnsafeCell<T>>);
|
||||||
|
unsafe impl<T> Send for UninitCell<T> {}
|
||||||
|
unsafe impl<T> Sync for UninitCell<T> {}
|
||||||
|
|
||||||
|
impl<T> UninitCell<T> {
|
||||||
|
pub const fn uninit() -> Self {
|
||||||
|
Self(MaybeUninit::uninit())
|
||||||
|
}
|
||||||
|
unsafe fn as_ptr(&self) -> *const T {
|
||||||
|
(*self.0.as_ptr()).get()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub unsafe fn as_mut_ptr(&self) -> *mut T {
|
||||||
|
(*self.0.as_ptr()).get()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub unsafe fn as_ref(&self) -> &T {
|
||||||
|
&*self.as_ptr()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub unsafe fn write(&self, val: T) {
|
||||||
|
ptr::write(self.as_mut_ptr(), val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Copy> UninitCell<T> {
|
||||||
|
pub unsafe fn read(&self) -> T {
|
||||||
|
ptr::read(self.as_mut_ptr())
|
||||||
|
}
|
||||||
|
}
|
@ -51,6 +51,9 @@ mod timer;
|
|||||||
#[cfg(feature = "std")]
|
#[cfg(feature = "std")]
|
||||||
mod driver_std;
|
mod driver_std;
|
||||||
|
|
||||||
|
#[cfg(feature = "wasm")]
|
||||||
|
mod driver_wasm;
|
||||||
|
|
||||||
pub use delay::{block_for, Delay};
|
pub use delay::{block_for, Delay};
|
||||||
pub use duration::Duration;
|
pub use duration::Duration;
|
||||||
pub use instant::Instant;
|
pub use instant::Instant;
|
||||||
|
@ -35,7 +35,7 @@ futures = { version = "0.3.17", default-features = false, features = ["async-awa
|
|||||||
rtt-target = { version = "0.3.1", features = ["cortex-m"] }
|
rtt-target = { version = "0.3.1", features = ["cortex-m"] }
|
||||||
heapless = { version = "0.7.5", default-features = false }
|
heapless = { version = "0.7.5", default-features = false }
|
||||||
rand_core = "0.6.3"
|
rand_core = "0.6.3"
|
||||||
critical-section = "0.2.1"
|
critical-section = "0.2.2"
|
||||||
|
|
||||||
micromath = "2.0.0"
|
micromath = "2.0.0"
|
||||||
|
|
||||||
|
17
examples/wasm/Cargo.toml
Normal file
17
examples/wasm/Cargo.toml
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
[package]
|
||||||
|
authors = ["Ulf Lilleengen <lulf@redhat.com>"]
|
||||||
|
edition = "2018"
|
||||||
|
name = "embassy-wasm-example"
|
||||||
|
version = "0.1.0"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
crate-type = ["cdylib"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
embassy = { version = "0.1.0", path = "../../embassy", features = ["log", "wasm"] }
|
||||||
|
|
||||||
|
wasm-logger = "0.2.0"
|
||||||
|
wasm-bindgen = "0.2"
|
||||||
|
web-sys = { version = "0.3", features = ["Document", "Element", "HtmlElement", "Node", "Window" ] }
|
||||||
|
log = "0.4.11"
|
||||||
|
critical-section = "0.2.2"
|
26
examples/wasm/README.md
Normal file
26
examples/wasm/README.md
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
# WASM example
|
||||||
|
|
||||||
|
Examples use a CLI tool named `wasm-pack` to build this example:
|
||||||
|
|
||||||
|
```
|
||||||
|
cargo install wasm-pack
|
||||||
|
```
|
||||||
|
|
||||||
|
## Building
|
||||||
|
|
||||||
|
To build the example, run:
|
||||||
|
|
||||||
|
```
|
||||||
|
wasm-pack build --target web
|
||||||
|
```
|
||||||
|
|
||||||
|
## Running
|
||||||
|
|
||||||
|
To run the example, start a webserver server the local folder:
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
python -m http.server
|
||||||
|
```
|
||||||
|
|
||||||
|
Then, open a browser at https://127.0.0.1:8000 and watch the ticker print entries to the window.
|
25
examples/wasm/index.html
Normal file
25
examples/wasm/index.html
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta content="text/html;charset=utf-8" http-equiv="Content-Type"/>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<!-- Note the usage of `type=module` here as this is an ES6 module -->
|
||||||
|
<script type="module">
|
||||||
|
// Use ES module import syntax to import functionality from the module
|
||||||
|
// that we have compiled.
|
||||||
|
//
|
||||||
|
// Note that the `default` import is an initialization function which
|
||||||
|
// will "boot" the module and make it ready to use. Currently browsers
|
||||||
|
// don't support natively imported WebAssembly as an ES module, but
|
||||||
|
// eventually the manual initialization won't be required!
|
||||||
|
import init from './pkg/embassy_wasm_example.js';
|
||||||
|
await init();
|
||||||
|
</script>
|
||||||
|
<h1>Log</h1>
|
||||||
|
<div>
|
||||||
|
<ul id="log">
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
37
examples/wasm/src/lib.rs
Normal file
37
examples/wasm/src/lib.rs
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
#![feature(type_alias_impl_trait)]
|
||||||
|
#![allow(incomplete_features)]
|
||||||
|
|
||||||
|
use embassy::{
|
||||||
|
executor::Spawner,
|
||||||
|
time::{Duration, Timer},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[embassy::task]
|
||||||
|
async fn ticker() {
|
||||||
|
let window = web_sys::window().expect("no global `window` exists");
|
||||||
|
|
||||||
|
let mut counter = 0;
|
||||||
|
loop {
|
||||||
|
let document = window.document().expect("should have a document on window");
|
||||||
|
let list = document
|
||||||
|
.get_element_by_id("log")
|
||||||
|
.expect("should have a log element");
|
||||||
|
|
||||||
|
let li = document
|
||||||
|
.create_element("li")
|
||||||
|
.expect("error creating list item element");
|
||||||
|
li.set_text_content(Some(&format!("tick {}", counter)));
|
||||||
|
|
||||||
|
list.append_child(&li).expect("error appending list item");
|
||||||
|
log::info!("tick {}", counter);
|
||||||
|
counter += 1;
|
||||||
|
|
||||||
|
Timer::after(Duration::from_secs(1)).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[embassy::main]
|
||||||
|
async fn main(spawner: Spawner) {
|
||||||
|
wasm_logger::init(wasm_logger::Config::default());
|
||||||
|
spawner.spawn(ticker()).unwrap();
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user