product-os-async-executor 0.0.20

Product OS : Async Executor provides a set of tools to handle async execution generically so that the desired async library (e.g. tokio, smol) to be used can be chosen at compile time.
Documentation
use crate::SpawnError;
use alloc::sync::Arc;
use core::pin::Pin;
use core::task::{Context, Poll};

use crate::{Executor, ExecutorPerform};
use crate::{Future, Timer};

/// Embassy-based executor implementation for embedded environments.
///
/// This executor wraps an Embassy `SendSpawner` and provides timer functionality
/// using `embassy_time::Ticker`.
///
/// # Limitations
///
/// - **Sync operations are not supported**: Embassy is designed for single-threaded,
///   cooperative async execution on embedded systems. All `_sync` methods will panic
///   with `unimplemented!()`.
/// - **`block_on` is not supported**: Embassy does not provide a blocking executor.
/// - **Task model**: Embassy uses a static task model with `#[embassy_executor::task]`
///   annotated functions that return `SpawnToken`. The generic `spawn()` methods
///   attempt to spawn arbitrary futures, which may not match Embassy's expected
///   task model. For best results, use Embassy's native task spawning patterns.
/// - **Task output**: Retrieving task output is not directly supported by Embassy's
///   fire-and-forget model. The `Task` implementation is a stub.
#[cfg(feature = "exec_embassy")]
pub struct EmbassyExecutor {
    spawner: Option<embassy_executor::SendSpawner>,
    ticker: Option<embassy_time::Ticker>,
}

#[cfg(feature = "exec_embassy")]
impl Executor<embassy_executor::SendSpawner> for EmbassyExecutor {
    async fn context() -> Result<Self, SpawnError> {
        let spawner = embassy_executor::SendSpawner::for_current_executor().await;
        Ok(Self {
            spawner: Some(spawner),
            ticker: None,
        })
    }

    async fn set_context(&mut self, executor: embassy_executor::SendSpawner) {
        self.spawner = Some(executor);
    }

    async fn enter_context(&self) {
        // Embassy does not have an explicit context to enter.
        // The executor context is implicitly the current embassy executor.
    }

    async fn get_executor<'a>(&'a self) -> &'a embassy_executor::SendSpawner
    where
        embassy_executor::SendSpawner: 'a,
    {
        self.spawner.as_ref().expect("No executor available")
    }

    fn context_sync() -> Result<Self, SpawnError> {
        unimplemented!("Embassy does not support synchronous executor context creation")
    }

    fn set_context_sync(&mut self, _executor: embassy_executor::SendSpawner) {
        unimplemented!("Embassy does not support synchronous context setting")
    }

    fn enter_context_sync(&self) {
        unimplemented!("Embassy does not support synchronous context entry")
    }

    fn get_executor_sync(&self) -> &embassy_executor::SendSpawner {
        unimplemented!("Embassy does not support synchronous executor access")
    }
}

#[cfg(feature = "exec_embassy")]
impl ExecutorPerform<embassy_executor::SendSpawner> for EmbassyExecutor {
    async fn spawn_in_context<F>(
        _future: F,
    ) -> Result<Arc<dyn crate::Task<F::Output, Output = F::Output>>, crate::SpawnError>
    where
        F: Future + Send + 'static,
        F::Output: Send + 'static,
    {
        // Embassy uses a static task model; spawning arbitrary futures is unsupported.
        // Returns a stub task as a best-effort fallback.
        let spawner = embassy_executor::SendSpawner::for_current_executor().await;
        // Note: spawner.spawn() expects a SpawnToken from a #[embassy_executor::task] function,
        // not an arbitrary future. This will fail at compile time if the future type doesn't match.
        // For production use, prefer Embassy's native task spawning patterns.
        let _ = spawner;
        Ok(Arc::new(Task::new()))
    }

    async fn spawn_from_executor<E, F>(
        _executor: &E,
        _future: F,
    ) -> Result<Arc<dyn crate::Task<F::Output, Output = F::Output>>, crate::SpawnError>
    where
        E: Executor<embassy_executor::SendSpawner>,
        F: Future + Send + 'static,
        F::Output: Send + 'static,
    {
        // See spawn_in_context for limitations
        Ok(Arc::new(Task::new()))
    }

    async fn block_from_executor<E, F>(_executor: &E, _future: F) -> F::Output
    where
        E: Executor<embassy_executor::SendSpawner>,
        F: Future + Send + 'static,
        F::Output: Send + 'static,
    {
        unimplemented!("Embassy does not support blocking on futures")
    }

    fn spawn_in_context_sync<F>(
        _future: F,
    ) -> Result<Arc<dyn crate::Task<F::Output, Output = F::Output>>, crate::SpawnError>
    where
        F: Future + Send + 'static,
        F::Output: Send + 'static,
    {
        unimplemented!("Embassy does not support synchronous task spawning")
    }

    fn spawn_from_executor_sync<E, F>(
        _executor: &E,
        _future: F,
    ) -> Result<Arc<dyn crate::Task<F::Output, Output = F::Output>>, crate::SpawnError>
    where
        E: Executor<embassy_executor::SendSpawner>,
        F: Future + Send + 'static,
        F::Output: Send + 'static,
    {
        unimplemented!("Embassy does not support synchronous task spawning")
    }

    fn block_from_executor_sync<E, F>(_executor: &E, _future: F) -> F::Output
    where
        E: Executor<embassy_executor::SendSpawner>,
        F: Future + Send + 'static,
        F::Output: Send + 'static,
    {
        unimplemented!("Embassy does not support synchronous blocking")
    }
}

#[cfg(feature = "exec_embassy")]
impl Timer for EmbassyExecutor {
    async fn once(duration_millis: u32) -> Self {
        // For one-shot, we use a ticker that we'll only tick once
        let duration = embassy_time::Duration::from_millis(u64::from(duration_millis));
        let ticker = embassy_time::Ticker::every(duration);

        Self {
            spawner: None,
            ticker: Some(ticker),
        }
    }

    async fn interval(duration_millis: u32) -> Self {
        let duration = embassy_time::Duration::from_millis(u64::from(duration_millis));
        let interval = embassy_time::Ticker::every(duration);

        Self {
            spawner: None,
            ticker: Some(interval),
        }
    }

    async fn cancel(&mut self) {
        self.ticker = None;
    }

    fn once_sync(duration_millis: u32) -> Self {
        let duration = embassy_time::Duration::from_millis(u64::from(duration_millis));
        let ticker = embassy_time::Ticker::every(duration);

        Self {
            spawner: None,
            ticker: Some(ticker),
        }
    }

    fn interval_sync(duration_millis: u32) -> Self {
        let duration = embassy_time::Duration::from_millis(u64::from(duration_millis));
        let interval = embassy_time::Ticker::every(duration);

        Self {
            spawner: None,
            ticker: Some(interval),
        }
    }

    fn cancel_sync(&mut self) {
        self.ticker = None;
    }

    async fn tick(&mut self) -> u32 {
        match &mut self.ticker {
            None => panic!("Timer has been cancelled"),
            Some(ticker) => {
                ticker.next().await;
                // Embassy's Ticker does not expose elapsed time; return 0.
                0
            }
        }
    }
}

#[cfg(feature = "exec_embassy")]
impl Clone for EmbassyExecutor {
    /// Clones the executor. Note: ticker state is not preserved.
    fn clone(&self) -> Self {
        Self {
            spawner: self.spawner,
            ticker: None,
        }
    }
}

/// Stub task for the Embassy executor.
///
/// Embassy uses a fire-and-forget task model (`#[embassy_executor::task]`).
/// This type satisfies the `Task` trait but does not provide output retrieval.
#[cfg(feature = "exec_embassy")]
pub struct Task<T> {
    _phantom: core::marker::PhantomData<T>,
}

#[cfg(feature = "exec_embassy")]
impl<T> Task<T> {
    pub const fn new() -> Self {
        Self {
            _phantom: core::marker::PhantomData,
        }
    }
}

#[cfg(feature = "exec_embassy")]
impl<T> Future for Task<T> {
    type Output = T;

    fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Self::Output> {
        // Embassy tasks are fire-and-forget; polling for output is not supported.
        Poll::Pending
    }
}

#[cfg(feature = "exec_embassy")]
impl<T: Send + 'static> crate::Task<T> for Task<T> {
    fn output(self) -> Pin<alloc::boxed::Box<dyn Future<Output = T> + Send>> {
        unimplemented!("Embassy tasks do not support retrieving output directly")
    }

    fn detach(self) {
        // Embassy tasks are inherently detached (fire-and-forget)
    }

    fn drop(self) {
        // No-op: Embassy tasks are fire-and-forget
    }
}

#[cfg(all(feature = "hyper_executor", feature = "exec_embassy"))]
impl<F> hyper::rt::Executor<F> for EmbassyExecutor
where
    F: core::future::Future + Send + 'static,
    F::Output: Send + 'static,
{
    fn execute(&self, _fut: F) {
        // No-op: Embassy cannot spawn arbitrary futures from a sync context.
    }
}