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);
}
}