frp-signal 0.1.0

Lifecycle and telemetry signal contracts for frp persistence flows.
Documentation
// ---------------------------------------------------------------------------
// Slot<T>
// ---------------------------------------------------------------------------

/// A named, single-value input receiver for a block port.
///
/// A `Slot` holds an optional pending value. The upstream edge writes a value
/// via [`Slot::receive`]; the block's execution reads and consumes it via
/// [`Slot::take`]. Once consumed the slot is empty until the next write.
#[derive(Debug, Clone)]
pub struct Slot<T> {
    name: String,
    value: Option<T>,
}

impl<T> Slot<T> {
    /// Create a new, empty `Slot` with the given port name.
    pub fn new(name: impl Into<String>) -> Self {
        Self { name: name.into(), value: None }
    }

    /// The port name this slot corresponds to.
    pub fn name(&self) -> &str {
        &self.name
    }

    /// Write a value into the slot, replacing any previously unreceived value.
    pub fn receive(&mut self, value: T) {
        self.value = Some(value);
    }

    /// Take the pending value out of the slot, leaving it empty.
    /// Returns `None` if no value is waiting.
    pub fn take(&mut self) -> Option<T> {
        self.value.take()
    }

    /// Peek at the pending value without consuming it.
    pub fn peek(&self) -> Option<&T> {
        self.value.as_ref()
    }

    /// Returns `true` if a value is waiting to be consumed.
    pub fn is_ready(&self) -> bool {
        self.value.is_some()
    }

    /// Drop any pending value, resetting the slot to empty.
    pub fn clear(&mut self) {
        self.value = None;
    }
}

impl<T: Default> Default for Slot<T> {
    fn default() -> Self {
        Self::new("")
    }
}

// ---------------------------------------------------------------------------
// Tests
// ---------------------------------------------------------------------------

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn new_slot_is_not_ready() {
        let s: Slot<i32> = Slot::new("x");
        assert!(!s.is_ready());
        assert_eq!(s.name(), "x");
    }

    #[test]
    fn receive_makes_ready() {
        let mut s = Slot::new("x");
        s.receive(42i32);
        assert!(s.is_ready());
    }

    #[test]
    fn take_consumes_value() {
        let mut s = Slot::new("x");
        s.receive(7i32);
        assert_eq!(s.take(), Some(7));
        assert!(!s.is_ready());
    }

    #[test]
    fn take_on_empty_returns_none() {
        let mut s: Slot<i32> = Slot::new("x");
        assert_eq!(s.take(), None);
    }

    #[test]
    fn peek_does_not_consume() {
        let mut s = Slot::new("x");
        s.receive(5i32);
        assert_eq!(s.peek(), Some(&5));
        assert!(s.is_ready()); // still ready
    }

    #[test]
    fn receive_replaces_unconsumed_value() {
        let mut s = Slot::new("x");
        s.receive(1i32);
        s.receive(2i32);
        assert_eq!(s.take(), Some(2));
    }

    #[test]
    fn clear_empties_slot() {
        let mut s = Slot::new("x");
        s.receive(99i32);
        s.clear();
        assert!(!s.is_ready());
    }
}