diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 999e908..2e67522 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -36,9 +36,6 @@ In any case, you will need: * **A supported development kit** - Drogue device is about ... devices, so having a kit that you can run will help you test and validate code for peripherals (see [examples](examples/) for what boards we have working examples for). -* **Rust Nightly** – Drogue-device relies on features only available in Rust nightly. You can use nightly either by running all - rust commands with `+nightly`, or just change default to nightly by running `rustup default nightly`. - * **An IDE** – Whatever works best for you. Eclipse, Emacs, IntelliJ, Vim, … [^1] should all be usable with this project. We do not require any specific IDE. We also do not commit any IDE specific files either. diff --git a/README.md b/README.md index 0754e37..829fd84 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Ector is an open source async, no-alloc actor framework for embedded devices. +# Ector is an open source async, no-alloc actor framework for embedded devices [![CI](https://github.com/drogue-iot/ector/actions/workflows/ci.yaml/badge.svg)](https://github.com/drogue-iot/ector/actions/workflows/ci.yaml) [![crates.io](https://img.shields.io/crates/v/ector.svg)](https://crates.io/crates/ector) @@ -15,11 +15,7 @@ Each actor has exclusive access to its own state and only communicates with othe ## Example -```rust -#![macro_use] -#![feature(generic_associated_types)] -#![feature(type_alias_impl_trait)] - +```rust ignore use ector::*; /// A Counter that we wish to create an Actor for. @@ -41,9 +37,8 @@ impl Actor for Counter { /// The following arguments are provided: /// * The address to 'self' /// * An inbox from which the actor can receive messages - async fn on_mount(&mut self, _: Address>, mut inbox: M) -> ! - where M: Inbox> { - { + async fn on_mount(&mut self, _: DynamicAddress, mut inbox: M) -> ! + where M: Inbox { loop { // Await the next message and increment the counter let _ = inbox.next().await; @@ -53,11 +48,11 @@ impl Actor for Counter { } /// The entry point of the application is using the embassy runtime. - #[embassy::main] - async fn main(spawner: embassy::executor::Spawner) { + #[embassy_executor::main] + async fn main(spawner: embassy_executor::Spawner) { // Mounting the Actor will spawn an embassy task - let addr = ector::actor!(spawner, counter, Counter, Counter { count 0 }); + let addr = ector::actor!(spawner, counter, Counter, Counter { count: 0 }); // The actor address may be used in any embassy task to communicate with the actor. let _ = addr.notify(Increment).await; @@ -66,19 +61,17 @@ impl Actor for Counter { ## Building -To build `ector`, you must install the [nightly rust toolchain](https://rustup.rs/). Once -installed, you can build and test the framework by running +You can build and test the framework by running -~~~shell +```shell cargo test -~~~ +``` ## Directory layout * `ector` - an actor framework * `macros` - macros used by drogue-device and application code - ## Contributing See the document [CONTRIBUTING.md](CONTRIBUTING.md). diff --git a/ector/Cargo.toml b/ector/Cargo.toml index 13738cb..610101d 100644 --- a/ector/Cargo.toml +++ b/ector/Cargo.toml @@ -1,7 +1,7 @@ [package] edition = "2021" name = "ector" -version = "0.5.0" +version = "0.6.0" description = "Ector is an open source async, no-alloc actor framework for embedded devices." documentation = "https://docs.rs/ector" readme = "../README.md" @@ -15,27 +15,40 @@ exclude = [".github"] doctest = false [dependencies] -embassy-executor = { version = "0.4", default-features = false } -embassy-sync = { version = "0.5", default-features = false } -atomic-polyfill = "1" +embassy-executor = { version = "0.6", default-features = false } +embassy-sync = { version = "0.6", default-features = false } +portable-atomic = { version = "1.3", default-features = false } log = { version = "0.4", optional = true } defmt = { version = "0.3", optional = true } ector-macros = { version = "0.5.0", path = "../macros" } futures = { version = "0.3", default-features = false } -static_cell = "1.0.0" +static_cell = "2.1" [dev-dependencies] -embassy-executor = { version = "0.4.0", default-features = false, features = ["integrated-timers", "arch-std", "executor-thread"]} -embassy-time = { version = "0.2.0", default-features = false, features = ["std"] } -futures = { version = "0.3", default-features = false, features = ["executor"] } -critical-section = { version = "1.1", features = ["std"] } +embassy-executor = { version = "0.6.0", default-features = false, features = [ + "integrated-timers", + "arch-std", + "executor-thread", +] } +embassy-time = { version = "0.3.2", default-features = false, features = [ + "std", +] } +futures = { version = "0.3.31", default-features = false, features = [ + "executor", +] } +critical-section = { version = "1.2.0", features = ["std"] } ector = { path = ".", features = ["std", "log", "time", "test-utils"] } [features] -default = [ "std", "log", "time" ] -std = ["embassy-executor/integrated-timers", "embassy-executor/arch-std", "embassy-time/std", "critical-section/std"] +default = ["std", "log", "time"] +std = [ + "embassy-executor/integrated-timers", + "embassy-executor/arch-std", + "embassy-time/std", + "critical-section/std", +] test-utils = [] time = [] diff --git a/ector/build.rs b/ector/build.rs new file mode 100644 index 0000000..dc2fee5 --- /dev/null +++ b/ector/build.rs @@ -0,0 +1,5 @@ +fn main() { + if std::env::var_os("CARGO_FEATURE_NIGHTLY").is_some() { + println!("cargo:rustc-cfg=nightly"); + } +} diff --git a/ector/examples/cancel_panic.rs b/ector/examples/cancel_panic.rs index 6c0f3d7..8e5fb77 100644 --- a/ector/examples/cancel_panic.rs +++ b/ector/examples/cancel_panic.rs @@ -1,10 +1,5 @@ //! Example where cancellation will cause a panic -#![macro_use] -#![feature(type_alias_impl_trait)] -#![feature(async_fn_in_trait)] -#![allow(incomplete_features)] - use { ector::*, embassy_time::{Duration, Timer}, diff --git a/ector/examples/macro-usage.rs b/ector/examples/macro-usage.rs index 0172686..f2c5403 100644 --- a/ector/examples/macro-usage.rs +++ b/ector/examples/macro-usage.rs @@ -1,8 +1,3 @@ -#![macro_use] -#![feature(type_alias_impl_trait)] -#![feature(async_fn_in_trait)] -#![allow(incomplete_features)] - use { ector::{mutex::NoopRawMutex, *}, embassy_time::{Duration, Timer}, diff --git a/ector/examples/pingpong.rs b/ector/examples/pingpong.rs index 1ef7321..49e8b0b 100644 --- a/ector/examples/pingpong.rs +++ b/ector/examples/pingpong.rs @@ -1,8 +1,3 @@ -#![macro_use] -#![feature(type_alias_impl_trait)] -#![feature(async_fn_in_trait)] -#![allow(incomplete_features)] - use { ector::*, embassy_time::{Duration, Ticker}, diff --git a/ector/examples/request.rs b/ector/examples/request.rs index f0e73dd..bcf148f 100644 --- a/ector/examples/request.rs +++ b/ector/examples/request.rs @@ -1,8 +1,3 @@ -#![macro_use] -#![feature(type_alias_impl_trait)] -#![feature(async_fn_in_trait)] -#![allow(incomplete_features)] - use { ector::*, embassy_time::{Duration, Timer}, diff --git a/ector/examples/send.rs b/ector/examples/send.rs index e6a9ef3..657cd5b 100644 --- a/ector/examples/send.rs +++ b/ector/examples/send.rs @@ -1,8 +1,3 @@ -#![macro_use] -#![feature(type_alias_impl_trait)] -#![feature(async_fn_in_trait)] -#![allow(incomplete_features)] - use ector::mutex::CriticalSectionRawMutex; use { diff --git a/ector/src/actor.rs b/ector/src/actor.rs index c1da024..66eee21 100644 --- a/ector/src/actor.rs +++ b/ector/src/actor.rs @@ -84,7 +84,12 @@ impl<'a, M, R> ActorRequest for DynamicSender<'a, Request> { // We guarantee that channel lives until we've been notified on it, at which // point its out of reach for the replier. - let reply_to = unsafe { core::mem::transmute(&sender) }; + let reply_to = unsafe { + core::mem::transmute::< + &embassy_sync::channel::DynamicSender<'_, R>, + &embassy_sync::channel::DynamicSender<'_, R>, + >(&sender) + }; let message = Request::new(message, reply_to); self.notify(message).await; let res = channel.receive().await; @@ -121,7 +126,12 @@ where // We guarantee that channel lives until we've been notified on it, at which // point its out of reach for the replier. - let reply_to = unsafe { core::mem::transmute(&sender) }; + let reply_to = unsafe { + core::mem::transmute::< + &embassy_sync::channel::DynamicSender<'_, R>, + &embassy_sync::channel::DynamicSender<'_, R>, + >(&sender) + }; let message = Request::new(message, reply_to); self.notify(message).await; let res = channel.receive().await; diff --git a/ector/src/lib.rs b/ector/src/lib.rs index 8c7ed94..b22db05 100644 --- a/ector/src/lib.rs +++ b/ector/src/lib.rs @@ -19,6 +19,58 @@ pub mod mutex { pub mod testutils; /// Spawn an actor given a spawner and the actors name, type and instance. +/// +/// actor! is a macro that simplifies the process of spawning an actor. It creates a new ActorContext and spawns the actor with the given spawner. +/// +/// # Arguments +/// +/// `actor!(spawner, name, type, instance, [mutex type], [queue size]);` +/// +/// | | | +/// | ----------- | ------------------------------------------------------------------------------------------------------ | +/// | spawner | The spawner to use to spawn the actor (i.e. embassy_executor::Spawner) | +/// | name | The name of the actor, used to generate the task name | +/// | type | The type of the actor, must implement the Actor trait | +/// | instance | The instance of the actor to spawn | +/// | mutex type | The type of mutex to use for the actor. (defaults to embassy_sync::blocking_mutex::raw::NoopRawMutex) | +/// | queue size | The size of the actor's message queue. (defaults to 1) | +/// +/// +/// # Example +/// +/// ```rust ignore +/// use ector::*; +/// +/// type Addr = DynamicAddress>; +/// +/// struct Server; +/// +/// impl Actor for Server { +/// type Message = Request; +/// async fn on_mount(&mut self, _: Addr, mut inbox: M) -> ! +/// where +/// M: Inbox, +/// { +/// println!("Server started!"); +/// +/// loop { +/// let motd = inbox.next().await; +/// let m = motd.as_ref().clone(); +/// motd.reply(m).await; +/// } +/// } +/// } +/// +/// #[embassy_executor::main] +/// async fn main(s: embassy_executor::Spawner) { +/// let server_addr: Addr = actor!(s, server, Server, Server).into(); +/// loop { +/// let r = server_addr.request("Hello".to_string()).await; +/// println!("Server returned {}", r); +/// } +/// } +/// +/// ``` #[macro_export] macro_rules! actor { ($spawner:ident, $name:ident, $ty:ty, $instance:expr) => {{ diff --git a/ector/src/testutils.rs b/ector/src/testutils.rs index ed8d670..4927f4b 100644 --- a/ector/src/testutils.rs +++ b/ector/src/testutils.rs @@ -1,9 +1,9 @@ use { crate::{Actor, DynamicAddress, Inbox}, - atomic_polyfill::{AtomicBool, Ordering}, core::{cell::RefCell, future::Future, pin::Pin}, embassy_executor::{raw, Spawner}, embassy_sync::{blocking_mutex::raw::CriticalSectionRawMutex, signal::Signal}, + portable_atomic::{AtomicBool, Ordering}, static_cell::StaticCell, std::{cell::UnsafeCell, marker::PhantomData, vec::Vec}, }; diff --git a/ector/tests/integration_tests.rs b/ector/tests/integration_tests.rs deleted file mode 100644 index 783a4d9..0000000 --- a/ector/tests/integration_tests.rs +++ /dev/null @@ -1,86 +0,0 @@ -#![macro_use] -#![feature(type_alias_impl_trait)] -#![feature(async_fn_in_trait)] -#![allow(incomplete_features)] - -#[cfg(feature = "std")] -mod tests { - use { - ector::*, - embassy_executor::Spawner, - std::{ - sync::{ - atomic::{AtomicU32, Ordering}, - mpsc, - }, - thread, - time::Duration, - }, - }; - - static INITIALIZED: AtomicU32 = AtomicU32::new(0); - - #[test] - fn test_device_setup() { - pub struct MyActor { - value: &'static AtomicU32, - } - - pub struct Add(u32); - impl Actor for MyActor { - type Message = Add; - - async fn on_mount<'m, M>(&'m mut self, _: DynamicAddress, mut inbox: M) -> ! - where - M: Inbox, - { - loop { - let message = inbox.next().await; - self.value.fetch_add(message.0, Ordering::SeqCst); - } - } - } - - #[embassy_executor::main] - async fn main(_s: Spawner) { - static ACTOR: ActorContext = ActorContext::new(); - - let a_addr = ACTOR.dyn_address(); - let _ = a_addr.try_notify(Add(10)); - ACTOR - .mount(MyActor { - value: &INITIALIZED, - }) - .await; - } - - std::thread::spawn(move || { - main(); - }); - - panic_after(Duration::from_secs(10), move || { - while INITIALIZED.load(Ordering::SeqCst) != 10 { - std::thread::sleep(Duration::from_secs(1)) - } - }) - } - - fn panic_after(d: Duration, f: F) -> T - where - T: Send + 'static, - F: FnOnce() -> T, - F: Send + 'static, - { - let (done_tx, done_rx) = mpsc::channel(); - let handle = thread::spawn(move || { - let val = f(); - done_tx.send(()).expect("Unable to send completion signal"); - val - }); - - match done_rx.recv_timeout(d) { - Ok(_) => handle.join().expect("Thread panicked"), - Err(_) => panic!("Thread took too long"), - } - } -} diff --git a/ector/tests/notifications.rs b/ector/tests/notifications.rs index 6e37c3e..fee5128 100644 --- a/ector/tests/notifications.rs +++ b/ector/tests/notifications.rs @@ -1,8 +1,6 @@ #![allow(dead_code)] #![allow(incomplete_features)] #![warn(unused_attributes)] -#![feature(type_alias_impl_trait)] -#![feature(async_fn_in_trait)] use ector::{ testutils::{DummyActor, *}, diff --git a/macros/Cargo.toml b/macros/Cargo.toml index 0b76ed8..b954d11 100644 --- a/macros/Cargo.toml +++ b/macros/Cargo.toml @@ -22,3 +22,4 @@ proc-macro2 = "1.0" [features] std = [] +nightly = [] diff --git a/macros/src/actor.rs b/macros/src/actor.rs index c4f36d7..3bc932f 100644 --- a/macros/src/actor.rs +++ b/macros/src/actor.rs @@ -187,7 +187,7 @@ fn transform_sig(sig: &mut Signature) { .predicates .push(parse_quote_spanned!(bound_span=> Self: 'm)); - for (_, arg) in sig.inputs.iter_mut().enumerate() { + for arg in sig.inputs.iter_mut() { match arg { FnArg::Receiver(arg) => { let s = arg.span(); diff --git a/macros/src/lib.rs b/macros/src/lib.rs index ad5217c..925412f 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -1,5 +1,5 @@ #![allow(incomplete_features)] -#![feature(proc_macro_diagnostic)] +#![cfg_attr(feature = "nightly", feature(proc_macro_diagnostic))] extern crate proc_macro; mod actor; diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 83a8f67..a95fc4e 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,5 +1,5 @@ # Before upgrading check that everything is available on all tier1 targets here: # https://rust-lang.github.io/rustup-components-history [toolchain] -channel = "nightly-2023-11-01" +channel = "stable" components = ["clippy"]