Skip to main content

spru_util/fsm/
mod.rs

1pub mod error;
2
3use std::mem;
4
5use derive_where::derive_where;
6pub use rust_fsm;
7use spru::common::error::AnyResult;
8use tagset::tagset;
9use telety::telety;
10
11use crate::{Strictness, cloned};
12
13/// A type which can be used in [Fsm].
14pub trait StateMachineTy: Clone + rust_fsm::StateMachineImpl + 'static {}
15
16impl<T: Clone + rust_fsm::StateMachineImpl + 'static> StateMachineTy for T {}
17
18/// A finite-state machine, useful for tracking things like turn phases.
19/// Powered by [rust_fsm]'s state machines.
20#[derive_where(Debug, Clone, Serialize, Deserialize; FSM::State)]
21pub struct Fsm<FSM: StateMachineTy>(FSM::State);
22
23impl<FSM: StateMachineTy> Fsm<FSM> {
24    pub fn current(&self) -> &FSM::State {
25        &self.0
26    }
27}
28
29pub fn default<FSM: StateMachineTy>() -> Create<FSM> {
30    create(FSM::INITIAL_STATE)
31}
32
33pub fn create<FSM: StateMachineTy>(initial_state: FSM::State) -> Create<FSM> {
34    cloned::create(Fsm(initial_state))
35}
36
37/// Transition using the provided input. Fails if the input is not
38/// permitted for the current state.
39pub fn transition<FSM: StateMachineTy>(input: FSM::Input) -> Transition<FSM> {
40    Transition {
41        input,
42        strictness: Strictness::AllOrError,
43    }
44}
45
46/// Attempt a transition, but ignore any error
47pub fn try_transition<FSM: StateMachineTy>(input: FSM::Input) -> Transition<FSM> {
48    Transition {
49        input,
50        strictness: Strictness::BestEffort,
51    }
52}
53
54/// Switch directly to the given state
55pub fn set<FSM: StateMachineTy>(new_state: FSM::State) -> Set<FSM> {
56    Set { new_state }
57}
58
59pub fn destroy<FSM: StateMachineTy>() -> Destroy<FSM> {
60    cloned::destroy()
61}
62
63#[telety(crate::fsm)]
64#[tagset(Create<FSM>)]
65#[tagset(Set<FSM>)]
66#[tagset(Transition<FSM>)]
67#[tagset(Destroy<FSM>)]
68#[tagset(reserved(..8))]
69pub struct Actions<FSM: StateMachineTy>;
70
71#[derive(Debug, Clone, crate::FromInfallible, thiserror::Error)]
72#[error("FSM error: {0}")]
73pub enum Error {
74    Transition(#[from] error::Transition),
75}
76
77pub type Create<FSM> = cloned::Create<Fsm<FSM>>;
78
79pub type Destroy<FSM> = cloned::Destroy<Fsm<FSM>>;
80
81#[derive_where(Debug, Clone, Serialize, Deserialize; FSM::Input)]
82#[derive(spru::action::Update)]
83#[must_use]
84pub struct Transition<FSM: StateMachineTy> {
85    input: FSM::Input,
86    strictness: Strictness,
87}
88
89impl<FSM> spru::action::Update for Transition<FSM>
90where
91    FSM: StateMachineTy<State: Clone, Input: Clone>,
92{
93    type T = Fsm<FSM>;
94    type Undo = Set<FSM>;
95
96    #[allow(refining_impl_trait)]
97    fn update(&self, value: &mut Self::T) -> AnyResult<Option<Self::Undo>> {
98        match FSM::transition(&value.0, &self.input) {
99            Some(new_state) => {
100                let old_state = mem::replace(&mut value.0, new_state);
101                Ok(Some(Set {
102                    new_state: old_state,
103                }))
104            }
105            None => match self.strictness {
106                Strictness::BestEffort => Ok(None),
107                Strictness::AllOrError => Err(error::Transition::TransitionImpossible(
108                    rust_fsm::TransitionImpossibleError,
109                )
110                .into()),
111            },
112        }
113    }
114}
115
116#[derive_where(Debug, Clone, Serialize, Deserialize; FSM::State)]
117#[derive(spru::action::Update)]
118#[must_use]
119pub struct Set<FSM: StateMachineTy> {
120    new_state: FSM::State,
121}
122
123impl<FSM> spru::action::Update for Set<FSM>
124where
125    FSM: StateMachineTy<State: Clone>,
126{
127    type T = Fsm<FSM>;
128    type Undo = Self;
129
130    #[allow(refining_impl_trait)]
131    fn update(&self, value: &mut Self::T) -> AnyResult<Self::Undo> {
132        let old_state = mem::replace(&mut value.0, self.new_state.clone());
133        Ok(Self {
134            new_state: old_state,
135        })
136    }
137}