roplat 0.2.0

roplat: just a robot operation system
Documentation
//! Rhythm abstraction and built-in rhythm sources.
//!
//! `Rhythm` drives execution cadence for a node domain. Different sources
//! (timer, event, counter, iterator, replay) share the same trait interface.

mod count_rhythm;
mod event_trigger;
mod iterator;
/// Recording/replay rhythm wrappers.
pub mod replay;
mod sys_timer;

use std::fmt::Debug;
use std::future::Future;

pub use count_rhythm::*;
pub use event_trigger::*;
pub use iterator::*;
pub use sys_timer::*;

use crate::RoplatError;

/// roplat rhythm driver trait.
///
/// `drive` takes ownership of nodes and returns them when the domain finishes.
/// This is critical for system codegen: it distinguishes externally passed nodes
/// (must be returned) from closure-local temporary nodes (can stay local).
///
/// # Contract Boundaries
/// - `drive` does not propagate errors via `Result`; encode recoverable errors in `Output`.
/// - `init` and `shutdown` may return errors for lifecycle hooks.
/// - `Yield` is produced by the rhythm source, and `Feed` is sent back by the domain.
///
/// # Example
/// ```rust
/// use roplat::rhythm::{CountRhythm, create_count_rhythm};
/// use roplat::Rhythm;
///
/// let (_, mut rhythm) = create_count_rhythm(3);
/// let nodes = 0usize;
/// let fut = rhythm.drive(nodes, |n, tick| async move {
///     ((), n + tick)
/// });
/// let (_out, n2) = futures::executor::block_on(fut);
/// assert!(n2 >= 0);
/// ```
pub trait Rhythm {
    /// Value produced on each rhythm tick.
    type Yield: Send;
    /// Value fed back from the execution domain to the rhythm source.
    type Feed;

    /// Output produced when rhythm execution completes.
    type Output;
    /// Error type used by lifecycle hooks.
    type Error: Into<RoplatError> + Debug;

    /// Drive the rhythm, returning both the rhythm output and the nodes back.
    ///
    /// Returning `N` enables the system codegen to distinguish nodes that are
    /// **passed in** to a rhythm domain (must come back out) from nodes that
    /// are **created inside** the closure (init/shutdown stays local). It also
    /// allows `Schedule::Spawn` to emit real `tokio::spawn` tasks that take
    /// ownership of their share of nodes — bypassing the `'static` bound that
    /// blocked the previous borrowed-tuple codegen.
    fn drive<N, F, Fut>(
        &mut self,
        nodes: N,
        op_domain: F,
    ) -> impl Future<Output = (Self::Output, N)> + Send
    where
        N: Send,
        F: FnMut(N, Self::Yield) -> Fut + Send,
        Fut: Future<Output = (Self::Feed, N)> + Send;

    /// Initialization hook.
    ///
    /// Default implementation does nothing and returns `Ok(())`.
    fn init<N, F>(
        &mut self,
        _nodes: N,
        _init: F,
    ) -> impl Future<Output = Result<(), Self::Error>> + Send
    where
        N: Send,
        F: FnMut() + Send,
    {
        async { Ok(()) }
    }

    /// Shutdown hook.
    ///
    /// Default implementation does nothing and returns `Ok(())`.
    fn shutdown<N, F>(
        &mut self,
        _nodes: N,
        _shutdown: F,
    ) -> impl Future<Output = Result<(), Self::Error>> + Send
    where
        N: Send,
        F: for<'a> FnMut(&'a mut N) + Send,
    {
        async { Ok(()) }
    }
}