1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147

/// Actions are just boxed immutable functions that take an argument of the event that triggered them
pub type Action<'a, S, E> = Box<Fn(&S,&E) + 'a>;

/// Predicates are used to filter down whether a transition can occur
pub type Predicate<'a, S, E> = Box<Fn(&S,&E) -> bool + 'a>;

/// Trait that should be trivially implementable for any C-Like Enum type
pub trait EnumTag: Copy {
	/// returns the discriminator tag for the enum (some_value as usize)
	fn tag_number(&self) -> usize;
	/// returns the highest discriminator tag for this enum
	fn max_tag_number() -> usize;
}

/// The Transition records, for a given current state, what event type triggers it to move to
/// what state, performing a specific action on the transition, filterable by a predicate function
struct Transition<'a, S: EnumTag, E: EnumTag> {
	next_state: S,
	action: Action<'a, S, E>,
}

/// The StateTransition records all Transitions for a given state
struct StateTransitions<'a, S: EnumTag, E: EnumTag> {
	edges: Vec<Option<Transition<'a, S, E>>>,
}

/// The Machine is the Finite State Machine, which has a current state and set of all valid
/// transitions
pub struct Machine<'a, S: EnumTag, E: EnumTag> {
	state: S,
	transitions: Vec<StateTransitions<'a, S, E>>,
}

impl<'a, S: EnumTag, E: EnumTag> Machine<'a, S, E> {
	/// Constructs a new FSM with a given initial state
	pub fn new(initial_state: S) -> Machine<'a, S, E> {
		let mut transitions = Vec::with_capacity(S::max_tag_number());

		for _ in 0..S::max_tag_number() + 1 {
			let mut edges = Vec::with_capacity(E::max_tag_number());

			for _ in 0..E::max_tag_number() + 1 {
				edges.push(None);
			}

			transitions.push(StateTransitions {
				edges: edges,
			});
		}

		Machine {
			state: initial_state,
			transitions: transitions,
		}
	}

	/// Registers a new valid transition with the FSM
	pub fn add_transition<F>(&mut self, in_state: S, on_event: E, next_state: S, action: F) -> bool
	where F: Fn(&S, &E) + 'a{
		let transition = &mut self.transitions[in_state.tag_number()];

		let edge = &mut transition.edges[on_event.tag_number()];

		if edge.is_none() {
			*edge = Some(Transition {
				action: Box::new(action),
				next_state: next_state,
			});
			true
		} else {
			false
		}
	}

	/// Retrieves a reference to the current state
	pub fn current_state(&self) -> S {
		self.state
	}

	/// Tick the State Machine with an Event
	pub fn on_event(&mut self, event_type: E) {
		let transition = &self.transitions[self.state.tag_number()];
		let edge = &transition.edges[event_type.tag_number()];
		if let &Some(ref t) = edge {
			(*t.action)(&self.state, &event_type);
			self.state = t.next_state;
		}
	}
}

#[cfg(test)]
mod test {
	use super::*;

	#[derive(Copy, Clone, Debug, Eq, PartialEq)]
	enum TurnStyleState {
		Locked,
		Unlocked,
	}

	#[derive(Copy, Clone, Debug, Eq, PartialEq)]
	enum TurnStyleEvent {
		Push,
		InsertCoin,
	}

	impl EnumTag for TurnStyleState {
		fn tag_number(&self) -> usize {
			*self as usize
		}
		fn max_tag_number() -> usize {
			TurnStyleState::Unlocked as usize
		}
	}

	impl EnumTag for TurnStyleEvent {
		fn tag_number(&self) -> usize {
			*self as usize
		}
		fn max_tag_number() -> usize {
			TurnStyleEvent::InsertCoin as usize
		}
	}

	#[test]
	fn test_machine() {
		let mut machine = Machine::new(TurnStyleState::Locked);
		machine.add_transition(
			TurnStyleState::Locked, TurnStyleEvent::InsertCoin,
			TurnStyleState::Unlocked, |_,_| println!("unlocked")
		);
		machine.add_transition(
			TurnStyleState::Unlocked, TurnStyleEvent::Push,
			TurnStyleState::Locked, |_,_| println!("locked")
		);
		assert!(machine.current_state() == TurnStyleState::Locked);
		machine.on_event(TurnStyleEvent::Push);
		assert!(machine.current_state() == TurnStyleState::Locked);
		machine.on_event(TurnStyleEvent::InsertCoin);
		assert!(machine.current_state() == TurnStyleState::Unlocked);
		machine.on_event(TurnStyleEvent::InsertCoin);
		assert!(machine.current_state() == TurnStyleState::Unlocked);
		machine.on_event(TurnStyleEvent::Push);
		assert!(machine.current_state() == TurnStyleState::Locked);
	}
}