aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorUlf Lilleengen <[email protected]>2021-09-13 14:35:40 +0200
committerUlf Lilleengen <[email protected]>2021-09-13 16:42:39 +0200
commite24528051b9ed26157bee93e32e37e7b06b8f4cc (patch)
treebb0c4f201a6a7df9099d27b3a64f92036d645d45
parentf1c35b40c74db489da8e04f1c2e87a1d4030c617 (diff)
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
-rw-r--r--.github/workflows/rust.yml2
-rw-r--r--embassy-macros/Cargo.toml1
-rw-r--r--embassy-macros/src/lib.rs79
-rw-r--r--embassy-nrf/Cargo.toml2
-rw-r--r--embassy-rp/Cargo.toml2
-rw-r--r--embassy-stm32/Cargo.toml2
-rw-r--r--embassy/Cargo.toml8
-rw-r--r--embassy/src/executor/arch/wasm.rs74
-rw-r--r--embassy/src/executor/mod.rs3
-rw-r--r--embassy/src/executor/raw/mod.rs2
-rw-r--r--embassy/src/lib.rs2
-rw-r--r--embassy/src/time/driver_wasm.rs135
-rw-r--r--embassy/src/time/mod.rs3
-rw-r--r--examples/stm32h7/Cargo.toml2
-rw-r--r--examples/wasm/Cargo.toml17
-rw-r--r--examples/wasm/README.md26
-rw-r--r--examples/wasm/index.html25
-rw-r--r--examples/wasm/src/lib.rs37
18 files changed, 414 insertions, 8 deletions
diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml
index 3ddda1a34..79a5ad796 100644
--- a/.github/workflows/rust.yml
+++ b/.github/workflows/rust.yml
@@ -94,6 +94,8 @@ jobs:
94 target: thumbv6m-none-eabi 94 target: thumbv6m-none-eabi
95 - package: examples/stm32g0 95 - package: examples/stm32g0
96 target: thumbv6m-none-eabi 96 target: thumbv6m-none-eabi
97 - package: examples/wasm
98 target: wasm32-unknown-unknown
97 steps: 99 steps:
98 - uses: actions/checkout@v2 100 - uses: actions/checkout@v2
99 with: 101 with:
diff --git a/embassy-macros/Cargo.toml b/embassy-macros/Cargo.toml
index b56f0f72f..955121230 100644
--- a/embassy-macros/Cargo.toml
+++ b/embassy-macros/Cargo.toml
@@ -18,3 +18,4 @@ nrf = []
18stm32 = [] 18stm32 = []
19rp = [] 19rp = []
20std = [] 20std = []
21wasm = []
diff --git a/embassy-macros/src/lib.rs b/embassy-macros/src/lib.rs
index ab327aced..708eed4cd 100644
--- a/embassy-macros/src/lib.rs
+++ b/embassy-macros/src/lib.rs
@@ -450,3 +450,82 @@ pub fn main(args: TokenStream, item: TokenStream) -> TokenStream {
450 }; 450 };
451 result.into() 451 result.into()
452} 452}
453
454#[cfg(feature = "wasm")]
455#[proc_macro_attribute]
456pub fn main(args: TokenStream, item: TokenStream) -> TokenStream {
457 let macro_args = syn::parse_macro_input!(args as syn::AttributeArgs);
458 let task_fn = syn::parse_macro_input!(item as syn::ItemFn);
459
460 let macro_args = match MainArgs::from_list(&macro_args) {
461 Ok(v) => v,
462 Err(e) => {
463 return TokenStream::from(e.write_errors());
464 }
465 };
466
467 let embassy_path = macro_args.embassy_prefix.append("embassy");
468
469 let mut fail = false;
470 if task_fn.sig.asyncness.is_none() {
471 task_fn
472 .sig
473 .span()
474 .unwrap()
475 .error("task functions must be async")
476 .emit();
477 fail = true;
478 }
479 if !task_fn.sig.generics.params.is_empty() {
480 task_fn
481 .sig
482 .span()
483 .unwrap()
484 .error("main function must not be generic")
485 .emit();
486 fail = true;
487 }
488
489 let args = task_fn.sig.inputs.clone();
490
491 if args.len() != 1 {
492 task_fn
493 .sig
494 .span()
495 .unwrap()
496 .error("main function must have one argument")
497 .emit();
498 fail = true;
499 }
500
501 if fail {
502 return TokenStream::new();
503 }
504
505 let task_fn_body = task_fn.block.clone();
506
507 let embassy_path = embassy_path.path();
508 let embassy_prefix_lit = macro_args.embassy_prefix.literal();
509
510 let result = quote! {
511 #[#embassy_path::task(embassy_prefix = #embassy_prefix_lit)]
512 async fn __embassy_main(#args) {
513 #task_fn_body
514 }
515
516 use wasm_bindgen::prelude::*;
517
518 #[wasm_bindgen(start)]
519 pub fn main() -> Result<(), JsValue> {
520 static EXECUTOR: #embassy_path::util::Forever<#embassy_path::executor::Executor> = #embassy_path::util::Forever::new();
521 let executor = EXECUTOR.put(#embassy_path::executor::Executor::new());
522
523 executor.start(|spawner| {
524 spawner.spawn(__embassy_main(spawner)).unwrap();
525 });
526
527 Ok(())
528 }
529 };
530 result.into()
531}
diff --git a/embassy-nrf/Cargo.toml b/embassy-nrf/Cargo.toml
index f42840133..edc1e22a3 100644
--- a/embassy-nrf/Cargo.toml
+++ b/embassy-nrf/Cargo.toml
@@ -45,7 +45,7 @@ cortex-m = "0.7.3"
45embedded-hal = "0.2.6" 45embedded-hal = "0.2.6"
46embedded-dma = "0.1.2" 46embedded-dma = "0.1.2"
47futures = { version = "0.3.17", default-features = false } 47futures = { version = "0.3.17", default-features = false }
48critical-section = "0.2.1" 48critical-section = "0.2.2"
49rand_core = "0.6.3" 49rand_core = "0.6.3"
50 50
51nrf52805-pac = { version = "0.10.1", optional = true, features = [ "rt" ] } 51nrf52805-pac = { version = "0.10.1", optional = true, features = [ "rt" ] }
diff --git a/embassy-rp/Cargo.toml b/embassy-rp/Cargo.toml
index 770005119..1702e0df8 100644
--- a/embassy-rp/Cargo.toml
+++ b/embassy-rp/Cargo.toml
@@ -27,7 +27,7 @@ defmt = { version = "0.2.3", optional = true }
27log = { version = "0.4.14", optional = true } 27log = { version = "0.4.14", optional = true }
28cortex-m-rt = ">=0.6.15,<0.8" 28cortex-m-rt = ">=0.6.15,<0.8"
29cortex-m = "0.7.3" 29cortex-m = "0.7.3"
30critical-section = "0.2.1" 30critical-section = "0.2.2"
31 31
32rp2040-pac2 = { git = "https://github.com/embassy-rs/rp2040-pac2", rev="9ad7223a48a065e612bc7dc7be5bf5bd0b41cfc4", features = ["rt"] } 32rp2040-pac2 = { git = "https://github.com/embassy-rs/rp2040-pac2", rev="9ad7223a48a065e612bc7dc7be5bf5bd0b41cfc4", features = ["rt"] }
33#rp2040-pac2 = { path = "../../rp/rp2040-pac2", features = ["rt"] } 33#rp2040-pac2 = { path = "../../rp/rp2040-pac2", features = ["rt"] }
diff --git a/embassy-stm32/Cargo.toml b/embassy-stm32/Cargo.toml
index b34d56854..3758c0626 100644
--- a/embassy-stm32/Cargo.toml
+++ b/embassy-stm32/Cargo.toml
@@ -21,7 +21,7 @@ futures = { version = "0.3.17", default-features = false, features = ["async-awa
21rand_core = "0.6.3" 21rand_core = "0.6.3"
22sdio-host = "0.5.0" 22sdio-host = "0.5.0"
23embedded-sdmmc = { git = "https://github.com/thalesfragoso/embedded-sdmmc-rs", branch = "async", optional = true } 23embedded-sdmmc = { git = "https://github.com/thalesfragoso/embedded-sdmmc-rs", branch = "async", optional = true }
24critical-section = "0.2.1" 24critical-section = "0.2.2"
25bare-metal = "1.0.0" 25bare-metal = "1.0.0"
26atomic-polyfill = "0.1.3" 26atomic-polyfill = "0.1.3"
27stm32-metapac = { version = "0.1.0", path = "../stm32-metapac", features = ["rt"] } 27stm32-metapac = { version = "0.1.0", path = "../stm32-metapac", features = ["rt"] }
diff --git a/embassy/Cargo.toml b/embassy/Cargo.toml
index ae06bc198..8aac861e3 100644
--- a/embassy/Cargo.toml
+++ b/embassy/Cargo.toml
@@ -8,6 +8,7 @@ resolver = "2"
8[features] 8[features]
9default = [] 9default = []
10std = ["futures/std", "embassy-traits/std", "time", "time-tick-1mhz", "embassy-macros/std"] 10std = ["futures/std", "embassy-traits/std", "time", "time-tick-1mhz", "embassy-macros/std"]
11wasm = ["wasm-bindgen", "js-sys", "embassy-macros/wasm", "wasm-timer", "time", "time-tick-1mhz"]
11 12
12# Enable `embassy::time` module. 13# Enable `embassy::time` module.
13# NOTE: This feature is only intended to be enabled by crates providing the time driver implementation. 14# 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 }
40embassy-macros = { version = "0.1.0", path = "../embassy-macros"} 41embassy-macros = { version = "0.1.0", path = "../embassy-macros"}
41embassy-traits = { version = "0.1.0", path = "../embassy-traits"} 42embassy-traits = { version = "0.1.0", path = "../embassy-traits"}
42atomic-polyfill = "0.1.3" 43atomic-polyfill = "0.1.3"
43critical-section = "0.2.1" 44critical-section = "0.2.2"
44embedded-hal = "0.2.6" 45embedded-hal = "0.2.6"
45heapless = "0.7.5" 46heapless = "0.7.5"
46 47
48# WASM dependencies
49wasm-bindgen = { version = "0.2.76", features = ["nightly"], optional = true }
50js-sys = { version = "0.3", optional = true }
51wasm-timer = { version = "0.2.5", optional = true }
52
47[dev-dependencies] 53[dev-dependencies]
48embassy = { path = ".", features = ["executor-agnostic"] } 54embassy = { path = ".", features = ["executor-agnostic"] }
49futures-executor = { version = "0.3.17", features = [ "thread-pool" ] } 55futures-executor = { version = "0.3.17", features = [ "thread-pool" ] }
diff --git a/embassy/src/executor/arch/wasm.rs b/embassy/src/executor/arch/wasm.rs
new file mode 100644
index 000000000..f069ebc3d
--- /dev/null
+++ b/embassy/src/executor/arch/wasm.rs
@@ -0,0 +1,74 @@
1use core::marker::PhantomData;
2use js_sys::Promise;
3use wasm_bindgen::prelude::*;
4
5use super::{
6 raw::{self, util::UninitCell},
7 Spawner,
8};
9
10/// WASM executor, wasm_bindgen to schedule tasks on the JS event loop.
11pub struct Executor {
12 inner: raw::Executor,
13 ctx: &'static WasmContext,
14 not_send: PhantomData<*mut ()>,
15}
16
17pub(crate) struct WasmContext {
18 promise: Promise,
19 closure: UninitCell<Closure<dyn FnMut(JsValue)>>,
20}
21
22impl WasmContext {
23 pub fn new() -> Self {
24 Self {
25 promise: Promise::resolve(&JsValue::undefined()),
26 closure: UninitCell::uninit(),
27 }
28 }
29}
30
31impl Executor {
32 /// Create a new Executor.
33 pub fn new() -> Self {
34 let ctx = &*Box::leak(Box::new(WasmContext::new()));
35 let inner = raw::Executor::new(
36 |p| unsafe {
37 let ctx = &*(p as *const () as *const WasmContext);
38 let _ = ctx.promise.then(ctx.closure.as_mut());
39 },
40 ctx as *const _ as _,
41 );
42 Self {
43 inner,
44 not_send: PhantomData,
45 ctx,
46 }
47 }
48
49 /// Run the executor.
50 ///
51 /// The `init` closure is called with a [`Spawner`] that spawns tasks on
52 /// this executor. Use it to spawn the initial task(s). After `init` returns,
53 /// the executor starts running the tasks.
54 ///
55 /// To spawn more tasks later, you may keep copies of the [`Spawner`] (it is `Copy`),
56 /// for example by passing it as an argument to the initial tasks.
57 ///
58 /// This function requires `&'static mut self`. This means you have to store the
59 /// Executor instance in a place where it'll live forever and grants you mutable
60 /// access. There's a few ways to do this:
61 ///
62 /// - a [Forever](crate::util::Forever) (safe)
63 /// - a `static mut` (unsafe)
64 /// - a local variable in a function you know never returns (like `fn main() -> !`), upgrading its lifetime with `transmute`. (unsafe)
65 pub fn start(&'static mut self, init: impl FnOnce(Spawner)) {
66 unsafe {
67 let executor = &self.inner;
68 self.ctx.closure.write(Closure::new(move |_| {
69 executor.poll();
70 }));
71 init(self.inner.spawner());
72 }
73 }
74}
diff --git a/embassy/src/executor/mod.rs b/embassy/src/executor/mod.rs
index 1f6bdd277..3ec24c13c 100644
--- a/embassy/src/executor/mod.rs
+++ b/embassy/src/executor/mod.rs
@@ -3,7 +3,8 @@
3#![deny(missing_docs)] 3#![deny(missing_docs)]
4 4
5#[cfg_attr(feature = "std", path = "arch/std.rs")] 5#[cfg_attr(feature = "std", path = "arch/std.rs")]
6#[cfg_attr(not(feature = "std"), path = "arch/arm.rs")] 6#[cfg_attr(feature = "wasm", path = "arch/wasm.rs")]
7#[cfg_attr(not(any(feature = "std", feature = "wasm")), path = "arch/arm.rs")]
7mod arch; 8mod arch;
8pub mod raw; 9pub mod raw;
9mod spawner; 10mod spawner;
diff --git a/embassy/src/executor/raw/mod.rs b/embassy/src/executor/raw/mod.rs
index 05fb29758..08de77739 100644
--- a/embassy/src/executor/raw/mod.rs
+++ b/embassy/src/executor/raw/mod.rs
@@ -10,7 +10,7 @@
10mod run_queue; 10mod run_queue;
11#[cfg(feature = "time")] 11#[cfg(feature = "time")]
12mod timer_queue; 12mod timer_queue;
13mod util; 13pub(crate) mod util;
14mod waker; 14mod waker;
15 15
16use atomic_polyfill::{AtomicU32, Ordering}; 16use atomic_polyfill::{AtomicU32, Ordering};
diff --git a/embassy/src/lib.rs b/embassy/src/lib.rs
index 847142285..2eadefb08 100644
--- a/embassy/src/lib.rs
+++ b/embassy/src/lib.rs
@@ -1,4 +1,4 @@
1#![cfg_attr(not(feature = "std"), no_std)] 1#![cfg_attr(not(any(feature = "std", feature = "wasm")), no_std)]
2#![feature(generic_associated_types)] 2#![feature(generic_associated_types)]
3#![feature(const_fn_trait_bound)] 3#![feature(const_fn_trait_bound)]
4#![feature(const_fn_fn_ptr_basics)] 4#![feature(const_fn_fn_ptr_basics)]
diff --git a/embassy/src/time/driver_wasm.rs b/embassy/src/time/driver_wasm.rs
new file mode 100644
index 000000000..0a9270823
--- /dev/null
+++ b/embassy/src/time/driver_wasm.rs
@@ -0,0 +1,135 @@
1use atomic_polyfill::{AtomicU8, Ordering};
2use std::cell::UnsafeCell;
3use std::mem::MaybeUninit;
4use std::ptr;
5use std::sync::{Mutex, Once};
6use wasm_bindgen::prelude::*;
7use wasm_timer::Instant as StdInstant;
8
9use crate::time::driver::{AlarmHandle, Driver};
10
11const ALARM_COUNT: usize = 4;
12
13struct AlarmState {
14 token: Option<f64>,
15 closure: Option<Closure<dyn FnMut() + 'static>>,
16}
17
18unsafe impl Send for AlarmState {}
19
20impl AlarmState {
21 const fn new() -> Self {
22 Self {
23 token: None,
24 closure: None,
25 }
26 }
27}
28
29#[wasm_bindgen]
30extern "C" {
31 fn setTimeout(closure: &Closure<dyn FnMut()>, millis: u32) -> f64;
32 fn clearTimeout(token: f64);
33}
34
35struct TimeDriver {
36 alarm_count: AtomicU8,
37
38 once: Once,
39 alarms: UninitCell<Mutex<[AlarmState; ALARM_COUNT]>>,
40 zero_instant: UninitCell<StdInstant>,
41}
42
43const ALARM_NEW: AlarmState = AlarmState::new();
44crate::time_driver_impl!(static DRIVER: TimeDriver = TimeDriver {
45 alarm_count: AtomicU8::new(0),
46 once: Once::new(),
47 alarms: UninitCell::uninit(),
48 zero_instant: UninitCell::uninit(),
49});
50
51impl TimeDriver {
52 fn init(&self) {
53 self.once.call_once(|| unsafe {
54 self.alarms.write(Mutex::new([ALARM_NEW; ALARM_COUNT]));
55 self.zero_instant.write(StdInstant::now());
56 });
57 }
58}
59
60impl Driver for TimeDriver {
61 fn now(&self) -> u64 {
62 self.init();
63
64 let zero = unsafe { self.zero_instant.read() };
65 StdInstant::now().duration_since(zero).as_micros() as u64
66 }
67
68 unsafe fn allocate_alarm(&self) -> Option<AlarmHandle> {
69 let id = self
70 .alarm_count
71 .fetch_update(Ordering::AcqRel, Ordering::Acquire, |x| {
72 if x < ALARM_COUNT as u8 {
73 Some(x + 1)
74 } else {
75 None
76 }
77 });
78
79 match id {
80 Ok(id) => Some(AlarmHandle::new(id)),
81 Err(_) => None,
82 }
83 }
84
85 fn set_alarm_callback(&self, alarm: AlarmHandle, callback: fn(*mut ()), ctx: *mut ()) {
86 self.init();
87 let mut alarms = unsafe { self.alarms.as_ref() }.lock().unwrap();
88 let alarm = &mut alarms[alarm.id() as usize];
89 alarm.closure.replace(Closure::new(move || {
90 callback(ctx);
91 }));
92 }
93
94 fn set_alarm(&self, alarm: AlarmHandle, timestamp: u64) {
95 self.init();
96 let mut alarms = unsafe { self.alarms.as_ref() }.lock().unwrap();
97 let alarm = &mut alarms[alarm.id() as usize];
98 let timeout = (timestamp - self.now()) as u32;
99 if let Some(token) = alarm.token {
100 clearTimeout(token);
101 }
102 alarm.token = Some(setTimeout(alarm.closure.as_ref().unwrap(), timeout / 1000));
103 }
104}
105
106pub(crate) struct UninitCell<T>(MaybeUninit<UnsafeCell<T>>);
107unsafe impl<T> Send for UninitCell<T> {}
108unsafe impl<T> Sync for UninitCell<T> {}
109
110impl<T> UninitCell<T> {
111 pub const fn uninit() -> Self {
112 Self(MaybeUninit::uninit())
113 }
114 unsafe fn as_ptr(&self) -> *const T {
115 (*self.0.as_ptr()).get()
116 }
117
118 pub unsafe fn as_mut_ptr(&self) -> *mut T {
119 (*self.0.as_ptr()).get()
120 }
121
122 pub unsafe fn as_ref(&self) -> &T {
123 &*self.as_ptr()
124 }
125
126 pub unsafe fn write(&self, val: T) {
127 ptr::write(self.as_mut_ptr(), val)
128 }
129}
130
131impl<T: Copy> UninitCell<T> {
132 pub unsafe fn read(&self) -> T {
133 ptr::read(self.as_mut_ptr())
134 }
135}
diff --git a/embassy/src/time/mod.rs b/embassy/src/time/mod.rs
index 6ce18d475..c8971bd13 100644
--- a/embassy/src/time/mod.rs
+++ b/embassy/src/time/mod.rs
@@ -51,6 +51,9 @@ mod timer;
51#[cfg(feature = "std")] 51#[cfg(feature = "std")]
52mod driver_std; 52mod driver_std;
53 53
54#[cfg(feature = "wasm")]
55mod driver_wasm;
56
54pub use delay::{block_for, Delay}; 57pub use delay::{block_for, Delay};
55pub use duration::Duration; 58pub use duration::Duration;
56pub use instant::Instant; 59pub use instant::Instant;
diff --git a/examples/stm32h7/Cargo.toml b/examples/stm32h7/Cargo.toml
index 94586b8ac..c581e212a 100644
--- a/examples/stm32h7/Cargo.toml
+++ b/examples/stm32h7/Cargo.toml
@@ -35,7 +35,7 @@ futures = { version = "0.3.17", default-features = false, features = ["async-awa
35rtt-target = { version = "0.3.1", features = ["cortex-m"] } 35rtt-target = { version = "0.3.1", features = ["cortex-m"] }
36heapless = { version = "0.7.5", default-features = false } 36heapless = { version = "0.7.5", default-features = false }
37rand_core = "0.6.3" 37rand_core = "0.6.3"
38critical-section = "0.2.1" 38critical-section = "0.2.2"
39 39
40micromath = "2.0.0" 40micromath = "2.0.0"
41 41
diff --git a/examples/wasm/Cargo.toml b/examples/wasm/Cargo.toml
new file mode 100644
index 000000000..77f513ffa
--- /dev/null
+++ b/examples/wasm/Cargo.toml
@@ -0,0 +1,17 @@
1[package]
2authors = ["Ulf Lilleengen <[email protected]>"]
3edition = "2018"
4name = "embassy-wasm-example"
5version = "0.1.0"
6
7[lib]
8crate-type = ["cdylib"]
9
10[dependencies]
11embassy = { version = "0.1.0", path = "../../embassy", features = ["log", "wasm"] }
12
13wasm-logger = "0.2.0"
14wasm-bindgen = "0.2"
15web-sys = { version = "0.3", features = ["Document", "Element", "HtmlElement", "Node", "Window" ] }
16log = "0.4.11"
17critical-section = "0.2.2"
diff --git a/examples/wasm/README.md b/examples/wasm/README.md
new file mode 100644
index 000000000..4bed4a797
--- /dev/null
+++ b/examples/wasm/README.md
@@ -0,0 +1,26 @@
1# WASM example
2
3Examples use a CLI tool named `wasm-pack` to build this example:
4
5```
6cargo install wasm-pack
7```
8
9## Building
10
11To build the example, run:
12
13```
14wasm-pack build --target web
15```
16
17## Running
18
19To run the example, start a webserver server the local folder:
20
21
22```
23python -m http.server
24```
25
26Then, open a browser at https://127.0.0.1:8000 and watch the ticker print entries to the window.
diff --git a/examples/wasm/index.html b/examples/wasm/index.html
new file mode 100644
index 000000000..05e8b29f8
--- /dev/null
+++ b/examples/wasm/index.html
@@ -0,0 +1,25 @@
1<!DOCTYPE html>
2<html>
3 <head>
4 <meta content="text/html;charset=utf-8" http-equiv="Content-Type"/>
5 </head>
6 <body>
7 <!-- Note the usage of `type=module` here as this is an ES6 module -->
8 <script type="module">
9 // Use ES module import syntax to import functionality from the module
10 // that we have compiled.
11 //
12 // Note that the `default` import is an initialization function which
13 // will "boot" the module and make it ready to use. Currently browsers
14 // don't support natively imported WebAssembly as an ES module, but
15 // eventually the manual initialization won't be required!
16 import init from './pkg/embassy_wasm_example.js';
17 await init();
18 </script>
19 <h1>Log</h1>
20 <div>
21 <ul id="log">
22 </ul>
23 </div>
24 </body>
25</html>
diff --git a/examples/wasm/src/lib.rs b/examples/wasm/src/lib.rs
new file mode 100644
index 000000000..0aa32a70b
--- /dev/null
+++ b/examples/wasm/src/lib.rs
@@ -0,0 +1,37 @@
1#![feature(type_alias_impl_trait)]
2#![allow(incomplete_features)]
3
4use embassy::{
5 executor::Spawner,
6 time::{Duration, Timer},
7};
8
9#[embassy::task]
10async fn ticker() {
11 let window = web_sys::window().expect("no global `window` exists");
12
13 let mut counter = 0;
14 loop {
15 let document = window.document().expect("should have a document on window");
16 let list = document
17 .get_element_by_id("log")
18 .expect("should have a log element");
19
20 let li = document
21 .create_element("li")
22 .expect("error creating list item element");
23 li.set_text_content(Some(&format!("tick {}", counter)));
24
25 list.append_child(&li).expect("error appending list item");
26 log::info!("tick {}", counter);
27 counter += 1;
28
29 Timer::after(Duration::from_secs(1)).await;
30 }
31}
32
33#[embassy::main]
34async fn main(spawner: Spawner) {
35 wasm_logger::init(wasm_logger::Config::default());
36 spawner.spawn(ticker()).unwrap();
37}