Last active
August 2, 2017 23:12
-
-
Save cameronp98/d707b7d7f5bf8c5c0010459b882d531d to your computer and use it in GitHub Desktop.
Example state machine with 3 states
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| use std::sync::mpsc::{channel, Sender, Receiver}; | |
| use std::thread::{self, JoinHandle}; | |
| #[derive(Debug, Clone, Copy, Eq, PartialEq)] | |
| enum Event { | |
| Wait, | |
| Work, | |
| Done, | |
| } | |
| #[derive(Debug)] | |
| struct EventListener { | |
| sender: Sender<Event>, | |
| } | |
| impl EventListener { | |
| fn new(sender: Sender<Event>) -> Self { | |
| EventListener { | |
| sender: sender, | |
| } | |
| } | |
| fn spawn(self, sequence: Vec<Event>) -> JoinHandle<()> { | |
| thread::spawn(move || { | |
| for event in sequence.into_iter() { | |
| self.sender.send(event).unwrap(); | |
| } | |
| }) | |
| } | |
| } | |
| #[derive(Debug)] | |
| struct Machine<S> { | |
| receiver: Receiver<Event>, | |
| state: S, | |
| } | |
| // State machine with 3 states | |
| // Waiting <--wait/work--> Working --done--> Done | |
| // ^-------------------wait------------------ | |
| #[derive(Debug)] | |
| enum MachineError { | |
| EndOfInput, | |
| UnexpectedEvent(Event), | |
| } | |
| type MachineResult<T> = Result<T, MachineError>; | |
| impl<S> Machine<S> { | |
| fn recv(&self) -> MachineResult<Event> { | |
| self.receiver.recv().map_err(|_| MachineError::EndOfInput); | |
| } | |
| fn expect(&self, event: Event) -> MachineResult<()> { | |
| let e = self.recv()?; | |
| if e != event { | |
| return Err(MachineError::UnexpectedEvent(e)); | |
| } | |
| Ok(()) | |
| } | |
| fn expect_one(&self, valid_events: &[Event]) -> MachineResult<Event> { | |
| let e = self.recv()?; | |
| if !valid_events.contains(&e) { | |
| return Err(MachineError::UnexpectedEvent(e)) | |
| } | |
| Ok(e) | |
| } | |
| } | |
| #[derive(Debug)] | |
| struct Waiting; | |
| impl Machine<Waiting> { | |
| fn try_work(self) -> MachineResult<Machine<Working>> { | |
| self.expect(Event::Work)?; | |
| Ok(self.into()) | |
| } | |
| } | |
| impl From<Machine<Done>> for Machine<Waiting> { | |
| fn from(machine: Machine<Done>) -> Machine<Waiting> { | |
| println!("DONE to WAIT"); | |
| Machine { | |
| receiver: machine.receiver, | |
| state: Waiting, | |
| } | |
| } | |
| } | |
| impl From<Machine<Working>> for Machine<Waiting> { | |
| fn from(machine: Machine<Working>) -> Machine<Waiting> { | |
| println!("WORK to WAIT"); | |
| Machine { | |
| receiver: machine.receiver, | |
| state: Waiting, | |
| } | |
| } | |
| } | |
| #[derive(Debug)] | |
| struct Working; | |
| impl Machine<Working> { | |
| fn try_finish(self) -> MachineResult<Machine<Done>> { | |
| // Machine<Working> can go forward to Done or backwards to Waiting | |
| let done = match self.expect_one(&[Event::Done, Event::Wait])? { | |
| Event::Done => { | |
| // Work is done, transition straight to Done | |
| self.into() | |
| }, | |
| Event::Wait => { | |
| // Work isn't done yet; wait, then try again | |
| let waiter: Machine<Waiting> = self.into(); | |
| waiter.try_work()?.try_finish()? | |
| } | |
| _ => unreachable!(), | |
| }; | |
| Ok(done) | |
| } | |
| } | |
| impl From<Machine<Waiting>> for Machine<Working> { | |
| fn from(machine: Machine<Waiting>) -> Machine<Working> { | |
| println!("WAIT to WORK"); | |
| Machine { | |
| receiver: machine.receiver, | |
| state: Working, | |
| } | |
| } | |
| } | |
| #[derive(Debug)] | |
| struct Done; | |
| impl Machine<Done> { | |
| // Machine always starts and ends in the Done state | |
| fn new(receiver: Receiver<Event>) -> Self { | |
| Machine { | |
| receiver: receiver, | |
| state: Done, | |
| } | |
| } | |
| fn try_wait(self) -> MachineResult<Machine<Waiting>> { | |
| self.expect(Event::Wait)?; | |
| Ok(self.into()) | |
| } | |
| } | |
| impl From<Machine<Working>> for Machine<Done> { | |
| fn from(machine: Machine<Working>) -> Machine<Done> { | |
| println!("WORK to DONE"); | |
| Machine { | |
| receiver: machine.receiver, | |
| state: Done, | |
| } | |
| } | |
| } | |
| // Complete sequences until error or exit | |
| fn run_machine(rx: Receiver<Event>) -> MachineResult<()> { | |
| // The sequence always starts/ends as Done | |
| let mut machine = Machine::new(rx); | |
| // Try and complete the state sequence until error or valid exit condition | |
| loop { | |
| // Try to enter the waiting state | |
| match machine.try_wait() { | |
| Ok(waiter) => { | |
| // Ok, now go through the sequence | |
| machine = waiter.try_work()?.try_finish()?; | |
| }, | |
| Err(e) => { | |
| // Failed to enter waiting state | |
| match e { | |
| MachineError::UnexpectedEvent(_) => { | |
| // Invalid input for the current state | |
| return Err(e) | |
| }, | |
| MachineError::EndOfInput => { | |
| // EOI while Done is the only valid exit condition | |
| return Ok(()) | |
| }, | |
| } | |
| } | |
| } | |
| } | |
| } | |
| pub fn main() { | |
| // Sender and receiver for events | |
| let (tx, rx) = channel(); | |
| // Create some events | |
| EventListener::new(tx).spawn(vec![ | |
| Event::Wait, | |
| Event::Work, | |
| Event::Wait, | |
| Event::Work, | |
| Event::Done, | |
| Event::Wait, | |
| Event::Work, | |
| Event::Done, | |
| ]); | |
| // Create a machine and receive events | |
| run_machine(rx).unwrap(); | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment