Skip to content

Commit

Permalink
Clippy, more renames, update documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
ivmarkov committed Oct 9, 2023
1 parent 010b310 commit 03f02a2
Show file tree
Hide file tree
Showing 2 changed files with 81 additions and 48 deletions.
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@

This crate ships a minimal async executor suitable for microcontrollers.

The implementation is a thin wrapper around [smol](::smol)'s [async-task](::async-task) crate.
The implementation is a thin wrapper around [smol](https://github.com/smol-rs/smol)'s [async-task](https://github.com/smol-rs/async-task) crate.

**Highlights**

- `no_std` (but does need `alloc`; for a `no_std` *and* "no_alloc" executor, look at [Embassy](::embassy), which statically pre-allocates all tasks);
- `no_std` (but does need `alloc`; for a `no_std` *and* "no_alloc" executor, look at [embassy-executor](https://github.com/embassy-rs/embassy/tree/main/embassy-executor), which statically pre-allocates all tasks);
(note also that the executor uses allocations in a limited way: when a new task is being spawn, as well as the executor itself);

- Follow closely the API of [smol](::smol)'s [async-executor](::async-executor) crate, so that it can serve as a (mostly) drop-in replacement;
- Follow closely the API of [smol](https://github.com/smol-rs/smol)'s [async-executor](https://github.com/smol-rs/async-executor) (`async_executor::LocalExecutor` specifically), so that it can serve as a (mostly) drop-in replacement;

- Does not assume an RTOS and can run completely bare-metal (or on top of an RTOS);

Expand All @@ -31,4 +31,4 @@ The implementation is a thin wrapper around [smol](::smol)'s [async-task](::asyn

- `WasmWakeup` implementation for the WASM event loop, compatible with WASM;

- `EventLoopWakeup` implementation for native event loops like those of GLIB, the Matter C++ SDK and others.
- `CEventLoopWakeup` implementation for native event loops like those of GLIB, the Matter C++ SDK and others.
121 changes: 77 additions & 44 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#![cfg_attr(not(feature = "std"), no_std)]

use core::fmt;
use core::future::{poll_fn, Future};
use core::marker::PhantomData;
use core::pin::pin;
Expand All @@ -21,15 +22,15 @@ use futures_lite::FutureExt;
pub use crate::std::*;

#[cfg(all(target_has_atomic = "ptr", target_has_atomic = "8"))]
pub use crate::eventloop::*;
pub use crate::ceventloop::*;

#[cfg(target_os = "espidf")]
pub use crate::espidf::*;

#[cfg(target_arch = "wasm32")]
pub use crate::wasm::*;

/// This trait captures the notion of an execution wakeup that needs to happen
/// `Wakeup` captures the notion of an execution wakeup that needs to happen
/// once a waker is awoken using `Waker::wake()`.
///
/// The implementation of the trait is operating system and
Expand Down Expand Up @@ -71,6 +72,7 @@ where
}
}

/// `Wait` is an extension of `Wakeup`.
/// `Wakeup` instances that provide a "sleep" facility need to also implement this trait.
/// Most `Wakeup` implementations should typically implement it.
///
Expand Down Expand Up @@ -130,18 +132,39 @@ where
}
}

/// An alternative to `Wait` that is typically implemented for executors that
/// are to be scheduled on an event-loop and thus do not follow the
#[derive(Debug)]
pub enum ScheduleError {
WrongContext,
}

impl fmt::Display for ScheduleError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Schedule error")
}
}

#[cfg(feature = "std")]
impl ::std::error::Error for ScheduleError {}

/// `Schedule` is an alternative to `Wait` that is typically implemented for executors that
/// are to be scheduled on an event-loop (or something similar to an event-loop) and thus do not follow the
/// "sleep the executor current thread until notified and then run the executor in the thread"
/// pattern, achieved by implementing the `Wait` trait.
pub trait Schedule: Wakeup {
fn schedule_poll_fn<P>(&self, poll_fn: P)
/// Schedules `poll_fn` to run at some later time, on the event-loop.
///
/// Calling this method MUST be done from within the "event-loop" as well, or else
/// the implementation will err with `ScheduleError`.
fn schedule_poll_fn<P>(&self, poll_fn: P) -> Result<(), ScheduleError>
where
P: FnMut() -> bool + 'static;

/// Schedules the future in the background and runs it until it becomes ready.
/// Schedules the future on the event-loop and runs it until it becomes ready.
/// At the end of the execution, calls the `on_complete` callback.
fn schedule<F, R>(&self, fut: F, on_complete: R)
///
/// Calling this method MUST be done from within the "event-loop" as well, or else
/// the implementation will err with `ScheduleError`.
fn schedule<F, R>(&self, fut: F, on_complete: R) -> Result<(), ScheduleError>
where
F: Future + 'static,
R: FnOnce(F::Output) + 'static,
Expand Down Expand Up @@ -171,15 +194,15 @@ pub trait Schedule: Wakeup {
}
};

self.schedule_poll_fn(poll_fn);
self.schedule_poll_fn(poll_fn)
}
}

impl<T> Schedule for &T
where
T: Schedule,
{
fn schedule_poll_fn<P>(&self, poll_fn: P)
fn schedule_poll_fn<P>(&self, poll_fn: P) -> Result<(), ScheduleError>
where
P: FnMut() -> bool + 'static,
{
Expand All @@ -191,7 +214,7 @@ impl<T> Schedule for &mut T
where
T: Schedule,
{
fn schedule_poll_fn<P>(&self, poll_fn: P)
fn schedule_poll_fn<P>(&self, poll_fn: P) -> Result<(), ScheduleError>
where
P: FnMut() -> bool + 'static,
{
Expand Down Expand Up @@ -228,7 +251,7 @@ where
/// - [WasmWakeup] implementation for the WASM event loop, compatible with WASM
/// (enable with feature `wasm`);
///
/// - [EventLoopWakeup] implementation for native event loops like those of GLIB, the Matter C++ SDK and others.
/// - [CEventLoopWakeup] implementation for native event loops like those of GLIB, the Matter C++ SDK and others.
pub struct LocalExecutor<'a, W, const C: usize = 64> {
#[cfg(feature = "crossbeam-queue")]
queue: Arc<crossbeam_queue::ArrayQueue<Runnable>>,
Expand Down Expand Up @@ -342,7 +365,7 @@ where
}

/// Polls the first task scheduled for execution by the executor.
fn poll_runnable(&self, ctx: &mut Context<'_>) -> Poll<Runnable> {
fn poll_runnable(&self, ctx: &Context<'_>) -> Poll<Runnable> {
self.poll_runnable_waker.register(ctx.waker());

if let Some(runnable) = self.try_runnable() {
Expand Down Expand Up @@ -499,8 +522,8 @@ mod std {
}
}

#[cfg(all(target_has_atomic = "ptr", target_has_atomic = "8"))]
mod eventloop {
#[cfg(target_has_atomic = "8")]
mod ceventloop {
use core::cell::UnsafeCell;
use core::marker::PhantomData;
use core::sync::atomic::{AtomicBool, Ordering};
Expand All @@ -513,26 +536,26 @@ mod eventloop {

use crate::*;

pub type EventLoopExecutor<'a, S> = LocalExecutor<'a, EventLoopWakeup<S>>;
pub type CEventLoopExecutor<'a, S> = LocalExecutor<'a, CEventLoopWakeup<S>>;

pub type Arg = *mut ();
pub type Callback = extern "C" fn(Arg);
pub type CEventLoopCallback = extern "C" fn(CEventLoopCallbackArg);
pub type CEventLoopCallbackArg = *mut ();

/// A generic `Monitor` instance useful for integrating into a native event loop, by scheduling
/// the execution of the tasks to happen in the event loop.
///
/// Only event loops that provide a way to schedule a piece of "work" in the event loop are
/// amenable to such integration. Typical event loops include the GLIB event loop, the Matter
/// C++ SDK event loop, and possibly many others.
pub struct EventLoopWakeup<S>(Arc<EventLoopWake<S>>, PhantomData<*const ()>);
pub struct CEventLoopWakeup<S>(Arc<CEventLoopWake<S>>, PhantomData<*const ()>);

impl<S> EventLoopWakeup<S>
impl<S> CEventLoopWakeup<S>
where
S: Fn(Callback, Arg) + 'static,
S: FnMut(CEventLoopCallback, CEventLoopCallbackArg),
{
pub fn new(scheduler: S) -> Self {
Self(
Arc::new(EventLoopWake {
Arc::new(CEventLoopWake {
scheduler,
scheduled: AtomicBool::new(false),
poller: UnsafeCell::new(None),
Expand All @@ -542,22 +565,22 @@ mod eventloop {
}
}

impl<S> Wakeup for EventLoopWakeup<S>
impl<S> Wakeup for CEventLoopWakeup<S>
where
S: Fn(Callback, Arg) + 'static,
S: Fn(CEventLoopCallback, CEventLoopCallbackArg) + 'static,
{
type Wake = EventLoopWake<S>;
type Wake = CEventLoopWake<S>;

fn waker(&self) -> Arc<Self::Wake> {
self.0.clone()
}
}

impl<S> Schedule for EventLoopWakeup<S>
impl<S> Schedule for CEventLoopWakeup<S>
where
S: Fn(Callback, Arg) + 'static,
S: Fn(CEventLoopCallback, CEventLoopCallbackArg) + 'static,
{
fn schedule_poll_fn<P>(&self, poll_fn: P)
fn schedule_poll_fn<P>(&self, poll_fn: P) -> Result<(), ScheduleError>
where
P: FnMut() -> bool + 'static,
{
Expand All @@ -566,22 +589,25 @@ mod eventloop {
*unsafe { ctx.poller() } = Some(Box::new(poll_fn));

self.waker().wake();

Ok(())
}
}

pub struct EventLoopWake<S> {
pub struct CEventLoopWake<S> {
scheduler: S,
scheduled: AtomicBool,
poller: UnsafeCell<Option<Box<dyn FnMut() -> bool + 'static>>>,
}

impl<S> EventLoopWake<S> {
impl<S> CEventLoopWake<S> {
#[allow(clippy::mut_from_ref)]
unsafe fn poller(&self) -> &mut Option<Box<dyn FnMut() -> bool + 'static>> {
self.poller.get().as_mut().unwrap()
}

extern "C" fn run(arg: Arg) {
let ctx = unsafe { Arc::from_raw(arg as *const EventLoopWake<S>) };
extern "C" fn run(arg: CEventLoopCallbackArg) {
let ctx = unsafe { Arc::from_raw(arg as *const CEventLoopWake<S>) };

ctx.scheduled.store(false, Ordering::SeqCst);

Expand All @@ -593,12 +619,14 @@ mod eventloop {
}
}

unsafe impl<S> Send for EventLoopWake<S> {}
unsafe impl<S> Sync for EventLoopWake<S> {}
// These are safe, because EventLoopWake cannot be constructed outside of this module, and does not have a public API
// All calls into the (potentially !Send, !Sync interior of )
unsafe impl<S> Send for CEventLoopWake<S> {}
unsafe impl<S> Sync for CEventLoopWake<S> {}

impl<S> Wake for EventLoopWake<S>
impl<S> Wake for CEventLoopWake<S>
where
S: Fn(Callback, Arg) + 'static,
S: Fn(CEventLoopCallback, CEventLoopCallbackArg) + 'static,
{
fn wake(self: Arc<Self>) {
if let Ok(false) =
Expand All @@ -612,11 +640,12 @@ mod eventloop {
}
}

#[cfg(target_arch = "wasm32")]
#[cfg(all(feature = "std", target_arch = "wasm32"))]
mod wasm {
use core::cell::RefCell;
use core::marker::PhantomData;

use ::std::sync::Mutex;

extern crate alloc;

use alloc::sync::Arc;
Expand All @@ -635,6 +664,8 @@ mod wasm {
closure: Option<Closure<dyn FnMut(JsValue)>>,
}

unsafe impl Send for Context {}

/// A `Wakeup` instance for web-assembly (WASM) based targets.
///
/// Works by integrating the wake instance (and thus the executor) into the WASM event loop.
Expand All @@ -645,10 +676,10 @@ mod wasm {
impl WasmWakeup {
pub fn new() -> Self {
Self(
Arc::new(RefCell::new(Context {
Arc::new(WasmWake(Mutex::new(Context {
promise: Promise::resolve(&JsValue::undefined()),
closure: None,
})),
}))),
PhantomData,
)
}
Expand All @@ -669,29 +700,31 @@ mod wasm {
}

impl Schedule for WasmWakeup {
fn schedule_poll_fn<P>(&self, mut poll_fn: P)
fn schedule_poll_fn<P>(&self, mut poll_fn: P) -> Result<(), ScheduleError>
where
P: FnMut() -> bool + 'static,
{
{
let ctx = self.0.clone();

ctx.borrow_mut().closure = Some(Closure::new(move |_| {
self.0 .0.lock().unwrap().closure = Some(Closure::new(move |_| {
if poll_fn() {
ctx.borrow_mut().closure = None;
ctx.0.lock().unwrap().closure = None;
}
}));
}

self.waker().wake();

Ok(())
}
}

pub struct WasmWake(RefCell<Context>);
pub struct WasmWake(Mutex<Context>);

impl Wake for WasmWake {
fn wake(self: Arc<Self>) {
let ctx = self.0.borrow_mut();
let ctx = self.0.lock().unwrap();

if let Some(closure) = ctx.closure.as_ref() {
let _ = ctx.promise.then(closure);
Expand Down

0 comments on commit 03f02a2

Please sign in to comment.