adar 0.3.0

Adar is a collection of architectural tools including flags, state machine, enum and tuple operations & more.
Documentation
use core::marker::PhantomData;

pub trait StateTypes<P1 = (), P2 = (), P3 = (), P4 = (), P5 = (), P6 = (), P7 = (), P8 = ()> {
    type States;
    type Context;
}

pub trait State<P1 = (), P2 = (), P3 = (), P4 = (), P5 = (), P6 = (), P7 = (), P8 = ()>
where
    Self: StateTypes<P1, P2, P3, P4, P5, P6, P7, P8>,
{
    #[allow(unused_variables)]
    #[inline(always)]
    fn on_enter(&mut self, context: &mut Self::Context) {}

    #[allow(unused_variables)]
    #[inline(always)]
    fn on_update(&mut self, context: &mut Self::Context) -> Option<Self::States> {
        None
    }

    #[allow(unused_variables)]
    #[inline(always)]
    fn on_leave(&mut self, context: &mut Self::Context) {}
}

pub trait Machine<P1 = (), P2 = (), P3 = (), P4 = (), P5 = (), P6 = (), P7 = (), P8 = ()>
where
    Self: StateTypes<P1, P2, P3, P4, P5, P6, P7, P8>,
{
    #[allow(unused_variables)]
    #[inline(always)]
    fn on_transition(&mut self, new_state: &Self::States, context: &mut Self::Context) {}
    #[allow(unused_variables)]
    #[inline(always)]
    fn on_update(&mut self, context: &mut Self::Context) {}
}

pub struct StateMachine<S, P1 = (), P2 = (), P3 = (), P4 = (), P5 = (), P6 = (), P7 = (), P8 = ()>
where
    S: State<P1, P2, P3, P4, P5, P6, P7, P8>
        + Machine<P1, P2, P3, P4, P5, P6, P7, P8>
        + StateTypes<P1, P2, P3, P4, P5, P6, P7, P8, States = S>,
{
    state: S::States,
    context: S::Context,
    phantom: PhantomData<(P1, P2, P3, P4, P5, P6, P7, P8)>,
}

impl<S, P1, P2, P3, P4, P5, P6, P7, P8> StateMachine<S, P1, P2, P3, P4, P5, P6, P7, P8>
where
    S: State<P1, P2, P3, P4, P5, P6, P7, P8>
        + Machine<P1, P2, P3, P4, P5, P6, P7, P8>
        + StateTypes<P1, P2, P3, P4, P5, P6, P7, P8, States = S>,
{
    pub fn new_context<S2>(
        state: S2,
        mut context: S::Context,
    ) -> StateMachine<S::States, P1, P2, P3, P4, P5, P6, P7, P8>
    where
        S2: StateTypes<P1, P2, P3, P4, P5, P6, P7, P8, States = S> + Into<S::States>,
    {
        let mut state = state.into() as S2::States;
        state.on_enter(&mut context);
        StateMachine::<S2::States, P1, P2, P3, P4, P5, P6, P7, P8> {
            state,
            context,
            phantom: PhantomData,
        }
    }

    #[inline(always)]
    pub fn new<S2>(state: S2) -> Self
    where
        S2: StateTypes<P1, P2, P3, P4, P5, P6, P7, P8, States = S> + Into<S::States>,
        S::Context: Default,
    {
        Self::new_context(state, S::Context::default())
    }

    pub fn run(&mut self) {
        while let Some(new_state) = State::on_update(&mut self.state, &mut self.context) {
            self.transition(new_state);
        }
    }

    pub fn update(&mut self) {
        if let Some(new_state) = State::on_update(&mut self.state, &mut self.context) {
            self.transition(new_state);
        }
    }

    pub fn transition(&mut self, new_state: impl Into<S>) {
        self.state.on_leave(&mut self.context);
        let new_state = new_state.into();
        self.state.on_transition(&new_state, &mut self.context);
        self.state = new_state;
        self.state.on_enter(&mut self.context);
    }

    pub fn context(&self) -> &S::Context {
        &self.context
    }

    pub fn context_mut(&mut self) -> &mut S::Context {
        &mut self.context
    }

    pub fn state(&self) -> &S::States {
        &self.state
    }

    pub fn state_mut(&mut self) -> &mut S::States {
        &mut self.state
    }
}

impl<S, P1, P2, P3, P4, P5, P6, P7, P8> HasEndState
    for StateMachine<S, P1, P2, P3, P4, P5, P6, P7, P8>
where
    S: State<P1, P2, P3, P4, P5, P6, P7, P8>
        + Machine<P1, P2, P3, P4, P5, P6, P7, P8>
        + StateTypes<P1, P2, P3, P4, P5, P6, P7, P8, States = S>
        + HasEndState,
{
    fn is_finished(&self) -> bool {
        self.state.is_finished()
    }
}

impl<S, P1, P2, P3, P4, P5, P6, P7, P8> core::fmt::Debug
    for StateMachine<S, P1, P2, P3, P4, P5, P6, P7, P8>
where
    S: State<P1, P2, P3, P4, P5, P6, P7, P8>
        + Machine<P1, P2, P3, P4, P5, P6, P7, P8>
        + StateTypes<P1, P2, P3, P4, P5, P6, P7, P8, States = S>,
    S::States: core::fmt::Debug,
    S::Context: core::fmt::Debug,
{
    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
        f.debug_struct("StateMachine")
            .field("state", &self.state)
            .field("context", &self.context)
            .finish()
    }
}

impl<S, P1, P2, P3, P4, P5, P6, P7, P8> Drop for StateMachine<S, P1, P2, P3, P4, P5, P6, P7, P8>
where
    S: State<P1, P2, P3, P4, P5, P6, P7, P8>
        + Machine<P1, P2, P3, P4, P5, P6, P7, P8>
        + StateTypes<P1, P2, P3, P4, P5, P6, P7, P8, States = S>,
{
    fn drop(&mut self) {
        self.state.on_leave(&mut self.context)
    }
}

#[derive(Debug)]
pub struct EndState;

impl StateTypes for EndState {
    type States = ();
    type Context = ();
}

impl State for EndState {}

pub trait HasEndState {
    fn is_finished(&self) -> bool;
}

#[cfg(test)]
mod test {
    use crate::{self as adar, prelude::*};
    use once_cell::sync::Lazy;
    use serial_test::serial;
    use std::sync::{Arc, Mutex};

    #[derive(Eq, PartialEq, Debug)]
    enum MockState {
        A,
        B,
        C,
    }

    type MockContext = u32;
    #[derive(Eq, PartialEq, Debug)]
    enum MockCall {
        OnEnter(MockContext),
        OnUpdate(MockContext),
        OnLeave(MockContext),
    }

    #[derive(Default, Clone)]
    struct Mock(Arc<Mutex<MockInner>>);

    static MOCK: Lazy<Mock> = Lazy::new(Mock::default);

    #[derive(Default)]
    struct MockInner {
        calls: Vec<(MockState, MockCall)>,
        b_transition: Option<Test>,
    }

    impl Mock {
        pub fn push(&self, state: MockState, call: MockCall) {
            self.0.lock().unwrap().calls.push((state, call));
        }

        pub fn take(&self) -> Vec<(MockState, MockCall)> {
            core::mem::take(&mut self.0.lock().unwrap().calls)
        }

        pub fn b_transition(&self, state: Test) {
            self.0.lock().unwrap().b_transition = Some(state);
        }
    }

    #[StateEnum(context=MockContext)]
    enum Test {
        A,
        B,
        C,
    }

    impl Machine for Test {}

    impl State for A {
        fn on_enter(&mut self, context: &mut Self::Context) {
            MOCK.push(MockState::A, MockCall::OnEnter(*context));
        }

        fn on_update(&mut self, context: &mut Self::Context) -> Option<Self::States> {
            MOCK.push(MockState::A, MockCall::OnUpdate(*context));
            None
        }

        fn on_leave(&mut self, context: &mut Self::Context) {
            MOCK.push(MockState::A, MockCall::OnLeave(*context));
        }
    }
    impl State for B {
        fn on_enter(&mut self, context: &mut Self::Context) {
            MOCK.push(MockState::B, MockCall::OnEnter(*context));
        }

        fn on_update(&mut self, context: &mut Self::Context) -> Option<Self::States> {
            MOCK.push(MockState::B, MockCall::OnUpdate(*context));
            MOCK.0.lock().unwrap().b_transition.take()
        }

        fn on_leave(&mut self, context: &mut Self::Context) {
            MOCK.push(MockState::B, MockCall::OnLeave(*context));
        }
    }
    impl State for C {
        fn on_enter(&mut self, context: &mut Self::Context) {
            MOCK.push(MockState::C, MockCall::OnEnter(*context));
        }

        fn on_update(&mut self, context: &mut Self::Context) -> Option<Self::States> {
            MOCK.push(MockState::C, MockCall::OnUpdate(*context));
            None
        }

        fn on_leave(&mut self, context: &mut Self::Context) {
            MOCK.push(MockState::C, MockCall::OnLeave(*context));
        }
    }

    #[StateEnum]
    #[derive(Debug)]
    enum TestDerive {
        A2,
    }
    impl Machine for TestDerive {}
    impl State for A2 {}

    #[StateEnum(context = Arc<Mutex<MockInner>>)]
    enum TestWithComplexContext {
        A3,
    }
    impl Machine for TestWithComplexContext {}
    impl State for A3 {}

    #[StateEnum(context = for<T> Option<T> where T: std::fmt::Debug)]
    enum TestWithGenericWithContext {
        A4,
    }
    impl Machine for TestWithGenericWithContext {}
    impl<T> State<T> for A4 where T: std::fmt::Debug {}

    #[test]
    fn test_macro_edge_cases() {
        // Note: Just to make sure they can be constructed
        let sm = StateMachine::new(A2);
        println!("{:?}", sm);
        StateMachine::new_context(A3, Arc::new(Mutex::new(MockInner::default())));
        StateMachine::new_context(A4, Some(()));
    }

    #[test]
    #[serial]
    fn test_external_transition_and_update() {
        let mut sm = StateMachine::new_context(A, 0);
        assert_eq!(MOCK.take(), vec![(MockState::A, MockCall::OnEnter(0))]);
        sm.update();
        assert_eq!(MOCK.take(), vec![(MockState::A, MockCall::OnUpdate(0))]);
        sm.transition(B);
        assert_eq!(
            MOCK.take(),
            vec![
                (MockState::A, MockCall::OnLeave(0)),
                (MockState::B, MockCall::OnEnter(0))
            ]
        );
        sm.update();
        assert_eq!(MOCK.take(), vec![(MockState::B, MockCall::OnUpdate(0))]);
        sm.transition(C);
        assert_eq!(
            MOCK.take(),
            vec![
                (MockState::B, MockCall::OnLeave(0)),
                (MockState::C, MockCall::OnEnter(0))
            ]
        );
        sm.update();
        assert_eq!(MOCK.take(), vec![(MockState::C, MockCall::OnUpdate(0))]);
        sm.update();
        assert_eq!(MOCK.take(), vec![(MockState::C, MockCall::OnUpdate(0))]);
        drop(sm);
        assert_eq!(MOCK.take(), vec![(MockState::C, MockCall::OnLeave(0))]);
    }

    #[test]
    #[serial]
    fn test_internal_transition_and_update() {
        let mut sm = StateMachine::new_context(B, 0);
        assert_eq!(MOCK.take(), vec![(MockState::B, MockCall::OnEnter(0))]);
        sm.update();
        assert_eq!(MOCK.take(), vec![(MockState::B, MockCall::OnUpdate(0))]);
        MOCK.b_transition(C.into());
        sm.update();
        assert_eq!(
            MOCK.take(),
            vec![
                (MockState::B, MockCall::OnUpdate(0)),
                (MockState::B, MockCall::OnLeave(0)),
                (MockState::C, MockCall::OnEnter(0))
            ]
        );
        drop(sm);
        assert_eq!(MOCK.take(), vec![(MockState::C, MockCall::OnLeave(0))]);
    }
}