pipecircuit 0.0.5

Correct. Robust. Fast. Portable.
Documentation
use core::any::type_name;

use crate::{
    NoopTurnObserver, Participant, StateMachine, TurnContext, TurnObservation, TurnObserver,
};

pub trait CircuitInputOf<Machine: StateMachine> {
    type Outcome;

    #[must_use = "circuit input outcomes must be delivered or settled"]
    fn turn<Observer: TurnObserver>(
        self,
        circuit: &mut Machine,
        turn: &mut TurnContext<Observer>,
    ) -> Self::Outcome;
}

mod private {
    use super::{CircuitInputOf, Participant, StateMachine};

    pub(super) trait TurnLaw<'message> {}

    impl<'message, Machine> TurnLaw<'message> for Machine
    where
        Machine: StateMachine + Participant,
        Machine::In<'message>: CircuitInputOf<Machine, Outcome = Machine::Out<'message>>,
    {
    }
}

#[doc(hidden)]
#[allow(private_bounds)]
pub trait TurnsCircuitInputToOutput<'message>:
    StateMachine + Participant + private::TurnLaw<'message>
{
    #[must_use = "circuit turn outcomes must be delivered or settled"]
    #[doc(hidden)]
    fn turn_boundary<Observer: TurnObserver>(
        &mut self,
        input: Self::In<'message>,
        turn: &mut TurnContext<Observer>,
    ) -> Self::Out<'message>;
}

impl<'message, Machine> TurnsCircuitInputToOutput<'message> for Machine
where
    Machine: StateMachine + Participant,
    Machine::In<'message>: CircuitInputOf<Machine, Outcome = Machine::Out<'message>>,
{
    #[inline(always)]
    fn turn_boundary<Observer: TurnObserver>(
        &mut self,
        input: Self::In<'message>,
        turn: &mut TurnContext<Observer>,
    ) -> Self::Out<'message> {
        input.turn(self, turn)
    }
}

pub trait Circuit:
    StateMachine + Participant + for<'message> TurnsCircuitInputToOutput<'message>
{
    #[must_use = "circuit turn outcomes must be delivered or settled"]
    #[inline(always)]
    fn turn<'message>(&mut self, input: Self::In<'message>) -> Self::Out<'message> {
        self.turn_in_context(input, &mut TurnContext::new(NoopTurnObserver))
    }

    #[must_use = "circuit turn outcomes must be delivered or settled"]
    #[doc(hidden)]
    #[inline(always)]
    fn turn_in_context<'message, Observer: TurnObserver>(
        &mut self,
        input: Self::In<'message>,
        turn: &mut TurnContext<Observer>,
    ) -> Self::Out<'message> {
        turn.observer_mut()
            .observe(TurnObservation::CircuitTurnBegan {
                circuit: type_name::<Self>(),
                input: type_name::<Self::In<'message>>(),
            });
        let output =
            <Self as TurnsCircuitInputToOutput<'message>>::turn_boundary(self, input, turn);
        turn.observer_mut()
            .observe(TurnObservation::CircuitTurnCompleted {
                circuit: type_name::<Self>(),
                output: type_name::<Self::Out<'message>>(),
            });
        output
    }

    fn with_observer<Observer: TurnObserver>(
        self,
        observer: Observer,
    ) -> ObservedCircuit<Self, Observer> {
        ObservedCircuit::new(self, observer)
    }
}

pub struct ObservedCircuit<InstalledCircuit, Observer> {
    circuit: InstalledCircuit,
    observer: Observer,
}

impl<InstalledCircuit, Observer> ObservedCircuit<InstalledCircuit, Observer> {
    pub const fn new(circuit: InstalledCircuit, observer: Observer) -> Self {
        Self { circuit, observer }
    }

    pub const fn circuit(&self) -> &InstalledCircuit {
        &self.circuit
    }

    pub const fn observer(&self) -> &Observer {
        &self.observer
    }

    pub const fn observer_mut(&mut self) -> &mut Observer {
        &mut self.observer
    }

    pub fn into_parts(self) -> (InstalledCircuit, Observer) {
        (self.circuit, self.observer)
    }
}

impl<InstalledCircuit: Circuit, Observer: TurnObserver>
    ObservedCircuit<InstalledCircuit, Observer>
{
    #[must_use = "observed circuit turn outcomes must be delivered or settled"]
    #[inline(always)]
    pub fn turn<'message>(
        &mut self,
        input: InstalledCircuit::In<'message>,
    ) -> InstalledCircuit::Out<'message> {
        self.circuit
            .turn_in_context(input, &mut TurnContext::new(&mut self.observer))
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::{Inline, InlineCircuit, NoopTurnObserver, Pipe, StepInputOf, TurnContext};

    #[derive(Debug, Eq, PartialEq)]
    struct TestCircuit;

    impl StateMachine for TestCircuit {}

    impl Participant for TestCircuit {
        type In<'message> = u8;
        type Out<'message> = u16;
    }

    impl CircuitInputOf<TestCircuit> for u8 {
        type Outcome = u16;

        fn turn<Observer: TurnObserver>(
            self,
            _circuit: &mut TestCircuit,
            _turn: &mut TurnContext<Observer>,
        ) -> Self::Outcome {
            u16::from(self)
        }
    }

    impl Circuit for TestCircuit {}

    struct Leaf;
    struct LeafInput(u8);

    impl StateMachine for Leaf {}

    impl Participant for Leaf {
        type In<'message> = LeafInput;
        type Out<'message> = u8;
    }

    impl StepInputOf<Leaf> for LeafInput {
        type Outcome = u8;

        fn step(self, _leaf: &mut Leaf) -> Self::Outcome {
            self.0
        }
    }

    struct ChildCircuit {
        leaf: Pipe<Leaf, Inline<Leaf>>,
    }

    struct ChildInput(u8);

    impl StateMachine for ChildCircuit {}

    impl Participant for ChildCircuit {
        type In<'message> = ChildInput;
        type Out<'message> = u8;
    }

    impl CircuitInputOf<ChildCircuit> for ChildInput {
        type Outcome = u8;

        fn turn<Observer: TurnObserver>(
            self,
            circuit: &mut ChildCircuit,
            turn: &mut TurnContext<Observer>,
        ) -> Self::Outcome {
            turn.send(&mut circuit.leaf, LeafInput(self.0))
        }
    }

    impl Circuit for ChildCircuit {}

    struct ParentCircuit {
        child: Pipe<ChildCircuit, InlineCircuit<ChildCircuit>>,
    }

    struct ParentInput(u8);

    impl StateMachine for ParentCircuit {}

    impl Participant for ParentCircuit {
        type In<'message> = ParentInput;
        type Out<'message> = u8;
    }

    impl CircuitInputOf<ParentCircuit> for ParentInput {
        type Outcome = u8;

        fn turn<Observer: TurnObserver>(
            self,
            circuit: &mut ParentCircuit,
            turn: &mut TurnContext<Observer>,
        ) -> Self::Outcome {
            turn.send(&mut circuit.child, ChildInput(self.0))
        }
    }

    impl Circuit for ParentCircuit {}

    #[derive(Default)]
    struct CountingObserver(usize);

    impl TurnObserver for CountingObserver {
        fn observe(&mut self, _observation: TurnObservation) {
            self.0 = self.0.saturating_add(1);
        }
    }

    #[test]
    fn circuit_turns_use_their_declared_participant_boundary() {
        assert_eq!(TestCircuit.turn(7), 7);
    }

    #[test]
    fn observed_circuits_reuse_one_observer_across_turns() {
        let mut program = TestCircuit.with_observer(CountingObserver::default());

        assert_eq!(program.turn(7), 7);
        assert_eq!(program.turn(8), 8);
        assert_eq!(program.circuit(), &TestCircuit);
        assert_eq!(program.observer().0, 4);
        assert_eq!(program.observer_mut().0, 4);
        let (_circuit, observer) = program.into_parts();
        assert_eq!(observer.0, 4);
    }

    #[test]
    fn one_context_crosses_an_inline_child_circuit_boundary() {
        let circuit = ParentCircuit {
            child: Pipe::new(InlineCircuit::new(ChildCircuit {
                leaf: Pipe::new(Inline::new(Leaf)),
            })),
        };
        let mut program = circuit.with_observer(CountingObserver::default());

        assert_eq!(program.turn(ParentInput(7)), 7);
        assert_eq!(program.observer().0, 10);
    }

    #[test]
    fn turn_context_accessors_preserve_the_observer() {
        let mut turn = TurnContext::new(CountingObserver::default());

        assert_eq!(turn.observer().0, 0);
        assert_eq!(turn.observer_mut().0, 0);
        assert_eq!(turn.into_observer().0, 0);
        let _turn = TurnContext::new(NoopTurnObserver);
    }
}