diff options
| -rw-r--r-- | .github/workflows/rust.yml | 2 | ||||
| -rw-r--r-- | embassy-macros/Cargo.toml | 1 | ||||
| -rw-r--r-- | embassy-macros/src/lib.rs | 79 | ||||
| -rw-r--r-- | embassy-nrf/Cargo.toml | 2 | ||||
| -rw-r--r-- | embassy-rp/Cargo.toml | 2 | ||||
| -rw-r--r-- | embassy-stm32/Cargo.toml | 2 | ||||
| -rw-r--r-- | embassy/Cargo.toml | 8 | ||||
| -rw-r--r-- | embassy/src/executor/arch/wasm.rs | 74 | ||||
| -rw-r--r-- | embassy/src/executor/mod.rs | 3 | ||||
| -rw-r--r-- | embassy/src/executor/raw/mod.rs | 2 | ||||
| -rw-r--r-- | embassy/src/lib.rs | 2 | ||||
| -rw-r--r-- | embassy/src/time/driver_wasm.rs | 135 | ||||
| -rw-r--r-- | embassy/src/time/mod.rs | 3 | ||||
| -rw-r--r-- | examples/stm32h7/Cargo.toml | 2 | ||||
| -rw-r--r-- | examples/wasm/Cargo.toml | 17 | ||||
| -rw-r--r-- | examples/wasm/README.md | 26 | ||||
| -rw-r--r-- | examples/wasm/index.html | 25 | ||||
| -rw-r--r-- | examples/wasm/src/lib.rs | 37 |
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 = [] | |||
| 18 | stm32 = [] | 18 | stm32 = [] |
| 19 | rp = [] | 19 | rp = [] |
| 20 | std = [] | 20 | std = [] |
| 21 | wasm = [] | ||
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] | ||
| 456 | pub 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(¯o_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" | |||
| 45 | embedded-hal = "0.2.6" | 45 | embedded-hal = "0.2.6" |
| 46 | embedded-dma = "0.1.2" | 46 | embedded-dma = "0.1.2" |
| 47 | futures = { version = "0.3.17", default-features = false } | 47 | futures = { version = "0.3.17", default-features = false } |
| 48 | critical-section = "0.2.1" | 48 | critical-section = "0.2.2" |
| 49 | rand_core = "0.6.3" | 49 | rand_core = "0.6.3" |
| 50 | 50 | ||
| 51 | nrf52805-pac = { version = "0.10.1", optional = true, features = [ "rt" ] } | 51 | nrf52805-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 } | |||
| 27 | log = { version = "0.4.14", optional = true } | 27 | log = { version = "0.4.14", optional = true } |
| 28 | cortex-m-rt = ">=0.6.15,<0.8" | 28 | cortex-m-rt = ">=0.6.15,<0.8" |
| 29 | cortex-m = "0.7.3" | 29 | cortex-m = "0.7.3" |
| 30 | critical-section = "0.2.1" | 30 | critical-section = "0.2.2" |
| 31 | 31 | ||
| 32 | rp2040-pac2 = { git = "https://github.com/embassy-rs/rp2040-pac2", rev="9ad7223a48a065e612bc7dc7be5bf5bd0b41cfc4", features = ["rt"] } | 32 | rp2040-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 | |||
| 21 | rand_core = "0.6.3" | 21 | rand_core = "0.6.3" |
| 22 | sdio-host = "0.5.0" | 22 | sdio-host = "0.5.0" |
| 23 | embedded-sdmmc = { git = "https://github.com/thalesfragoso/embedded-sdmmc-rs", branch = "async", optional = true } | 23 | embedded-sdmmc = { git = "https://github.com/thalesfragoso/embedded-sdmmc-rs", branch = "async", optional = true } |
| 24 | critical-section = "0.2.1" | 24 | critical-section = "0.2.2" |
| 25 | bare-metal = "1.0.0" | 25 | bare-metal = "1.0.0" |
| 26 | atomic-polyfill = "0.1.3" | 26 | atomic-polyfill = "0.1.3" |
| 27 | stm32-metapac = { version = "0.1.0", path = "../stm32-metapac", features = ["rt"] } | 27 | stm32-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] |
| 9 | default = [] | 9 | default = [] |
| 10 | std = ["futures/std", "embassy-traits/std", "time", "time-tick-1mhz", "embassy-macros/std"] | 10 | std = ["futures/std", "embassy-traits/std", "time", "time-tick-1mhz", "embassy-macros/std"] |
| 11 | wasm = ["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 } | |||
| 40 | embassy-macros = { version = "0.1.0", path = "../embassy-macros"} | 41 | embassy-macros = { version = "0.1.0", path = "../embassy-macros"} |
| 41 | embassy-traits = { version = "0.1.0", path = "../embassy-traits"} | 42 | embassy-traits = { version = "0.1.0", path = "../embassy-traits"} |
| 42 | atomic-polyfill = "0.1.3" | 43 | atomic-polyfill = "0.1.3" |
| 43 | critical-section = "0.2.1" | 44 | critical-section = "0.2.2" |
| 44 | embedded-hal = "0.2.6" | 45 | embedded-hal = "0.2.6" |
| 45 | heapless = "0.7.5" | 46 | heapless = "0.7.5" |
| 46 | 47 | ||
| 48 | # WASM dependencies | ||
| 49 | wasm-bindgen = { version = "0.2.76", features = ["nightly"], optional = true } | ||
| 50 | js-sys = { version = "0.3", optional = true } | ||
| 51 | wasm-timer = { version = "0.2.5", optional = true } | ||
| 52 | |||
| 47 | [dev-dependencies] | 53 | [dev-dependencies] |
| 48 | embassy = { path = ".", features = ["executor-agnostic"] } | 54 | embassy = { path = ".", features = ["executor-agnostic"] } |
| 49 | futures-executor = { version = "0.3.17", features = [ "thread-pool" ] } | 55 | futures-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 @@ | |||
| 1 | use core::marker::PhantomData; | ||
| 2 | use js_sys::Promise; | ||
| 3 | use wasm_bindgen::prelude::*; | ||
| 4 | |||
| 5 | use super::{ | ||
| 6 | raw::{self, util::UninitCell}, | ||
| 7 | Spawner, | ||
| 8 | }; | ||
| 9 | |||
| 10 | /// WASM executor, wasm_bindgen to schedule tasks on the JS event loop. | ||
| 11 | pub struct Executor { | ||
| 12 | inner: raw::Executor, | ||
| 13 | ctx: &'static WasmContext, | ||
| 14 | not_send: PhantomData<*mut ()>, | ||
| 15 | } | ||
| 16 | |||
| 17 | pub(crate) struct WasmContext { | ||
| 18 | promise: Promise, | ||
| 19 | closure: UninitCell<Closure<dyn FnMut(JsValue)>>, | ||
| 20 | } | ||
| 21 | |||
| 22 | impl WasmContext { | ||
| 23 | pub fn new() -> Self { | ||
| 24 | Self { | ||
| 25 | promise: Promise::resolve(&JsValue::undefined()), | ||
| 26 | closure: UninitCell::uninit(), | ||
| 27 | } | ||
| 28 | } | ||
| 29 | } | ||
| 30 | |||
| 31 | impl 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")] | ||
| 7 | mod arch; | 8 | mod arch; |
| 8 | pub mod raw; | 9 | pub mod raw; |
| 9 | mod spawner; | 10 | mod 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 @@ | |||
| 10 | mod run_queue; | 10 | mod run_queue; |
| 11 | #[cfg(feature = "time")] | 11 | #[cfg(feature = "time")] |
| 12 | mod timer_queue; | 12 | mod timer_queue; |
| 13 | mod util; | 13 | pub(crate) mod util; |
| 14 | mod waker; | 14 | mod waker; |
| 15 | 15 | ||
| 16 | use atomic_polyfill::{AtomicU32, Ordering}; | 16 | use 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 @@ | |||
| 1 | use atomic_polyfill::{AtomicU8, Ordering}; | ||
| 2 | use std::cell::UnsafeCell; | ||
| 3 | use std::mem::MaybeUninit; | ||
| 4 | use std::ptr; | ||
| 5 | use std::sync::{Mutex, Once}; | ||
| 6 | use wasm_bindgen::prelude::*; | ||
| 7 | use wasm_timer::Instant as StdInstant; | ||
| 8 | |||
| 9 | use crate::time::driver::{AlarmHandle, Driver}; | ||
| 10 | |||
| 11 | const ALARM_COUNT: usize = 4; | ||
| 12 | |||
| 13 | struct AlarmState { | ||
| 14 | token: Option<f64>, | ||
| 15 | closure: Option<Closure<dyn FnMut() + 'static>>, | ||
| 16 | } | ||
| 17 | |||
| 18 | unsafe impl Send for AlarmState {} | ||
| 19 | |||
| 20 | impl AlarmState { | ||
| 21 | const fn new() -> Self { | ||
| 22 | Self { | ||
| 23 | token: None, | ||
| 24 | closure: None, | ||
| 25 | } | ||
| 26 | } | ||
| 27 | } | ||
| 28 | |||
| 29 | #[wasm_bindgen] | ||
| 30 | extern "C" { | ||
| 31 | fn setTimeout(closure: &Closure<dyn FnMut()>, millis: u32) -> f64; | ||
| 32 | fn clearTimeout(token: f64); | ||
| 33 | } | ||
| 34 | |||
| 35 | struct TimeDriver { | ||
| 36 | alarm_count: AtomicU8, | ||
| 37 | |||
| 38 | once: Once, | ||
| 39 | alarms: UninitCell<Mutex<[AlarmState; ALARM_COUNT]>>, | ||
| 40 | zero_instant: UninitCell<StdInstant>, | ||
| 41 | } | ||
| 42 | |||
| 43 | const ALARM_NEW: AlarmState = AlarmState::new(); | ||
| 44 | crate::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 | |||
| 51 | impl 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 | |||
| 60 | impl 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 | |||
| 106 | pub(crate) struct UninitCell<T>(MaybeUninit<UnsafeCell<T>>); | ||
| 107 | unsafe impl<T> Send for UninitCell<T> {} | ||
| 108 | unsafe impl<T> Sync for UninitCell<T> {} | ||
| 109 | |||
| 110 | impl<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 | |||
| 131 | impl<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")] |
| 52 | mod driver_std; | 52 | mod driver_std; |
| 53 | 53 | ||
| 54 | #[cfg(feature = "wasm")] | ||
| 55 | mod driver_wasm; | ||
| 56 | |||
| 54 | pub use delay::{block_for, Delay}; | 57 | pub use delay::{block_for, Delay}; |
| 55 | pub use duration::Duration; | 58 | pub use duration::Duration; |
| 56 | pub use instant::Instant; | 59 | pub 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 | |||
| 35 | rtt-target = { version = "0.3.1", features = ["cortex-m"] } | 35 | rtt-target = { version = "0.3.1", features = ["cortex-m"] } |
| 36 | heapless = { version = "0.7.5", default-features = false } | 36 | heapless = { version = "0.7.5", default-features = false } |
| 37 | rand_core = "0.6.3" | 37 | rand_core = "0.6.3" |
| 38 | critical-section = "0.2.1" | 38 | critical-section = "0.2.2" |
| 39 | 39 | ||
| 40 | micromath = "2.0.0" | 40 | micromath = "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] | ||
| 2 | authors = ["Ulf Lilleengen <[email protected]>"] | ||
| 3 | edition = "2018" | ||
| 4 | name = "embassy-wasm-example" | ||
| 5 | version = "0.1.0" | ||
| 6 | |||
| 7 | [lib] | ||
| 8 | crate-type = ["cdylib"] | ||
| 9 | |||
| 10 | [dependencies] | ||
| 11 | embassy = { version = "0.1.0", path = "../../embassy", features = ["log", "wasm"] } | ||
| 12 | |||
| 13 | wasm-logger = "0.2.0" | ||
| 14 | wasm-bindgen = "0.2" | ||
| 15 | web-sys = { version = "0.3", features = ["Document", "Element", "HtmlElement", "Node", "Window" ] } | ||
| 16 | log = "0.4.11" | ||
| 17 | critical-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 | |||
| 3 | Examples use a CLI tool named `wasm-pack` to build this example: | ||
| 4 | |||
| 5 | ``` | ||
| 6 | cargo install wasm-pack | ||
| 7 | ``` | ||
| 8 | |||
| 9 | ## Building | ||
| 10 | |||
| 11 | To build the example, run: | ||
| 12 | |||
| 13 | ``` | ||
| 14 | wasm-pack build --target web | ||
| 15 | ``` | ||
| 16 | |||
| 17 | ## Running | ||
| 18 | |||
| 19 | To run the example, start a webserver server the local folder: | ||
| 20 | |||
| 21 | |||
| 22 | ``` | ||
| 23 | python -m http.server | ||
| 24 | ``` | ||
| 25 | |||
| 26 | Then, 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 | |||
| 4 | use embassy::{ | ||
| 5 | executor::Spawner, | ||
| 6 | time::{Duration, Timer}, | ||
| 7 | }; | ||
| 8 | |||
| 9 | #[embassy::task] | ||
| 10 | async 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] | ||
| 34 | async fn main(spawner: Spawner) { | ||
| 35 | wasm_logger::init(wasm_logger::Config::default()); | ||
| 36 | spawner.spawn(ticker()).unwrap(); | ||
| 37 | } | ||
