1use crate::error::{RuntimeError, RuntimeResult};
14use crate::ids::{EventName, StateName};
15use crate::registry::NameRegistry;
16
17pub trait RuntimeState {
19 fn name(&self) -> &StateName;
21}
22
23#[derive(Debug, Clone, PartialEq, Eq)]
25pub struct StateTransition {
26 pub from: StateName,
28 pub event: EventName,
30 pub to: StateName,
32}
33
34#[derive(Debug, Clone)]
36pub struct StateMachine {
37 current: StateName,
38 transitions: Vec<StateTransition>,
39}
40
41impl StateMachine {
42 pub fn new(initial: StateName) -> Self {
44 Self {
45 current: initial,
46 transitions: Vec::new(),
47 }
48 }
49
50 pub fn current(&self) -> &StateName {
52 &self.current
53 }
54
55 pub fn add_transition(&mut self, transition: StateTransition) -> RuntimeResult<()> {
57 let duplicated = self
58 .transitions
59 .iter()
60 .any(|existing| existing.from == transition.from && existing.event == transition.event);
61 if duplicated {
62 return Err(RuntimeError::DuplicateStateTransition);
63 }
64
65 self.transitions.push(transition);
66 Ok(())
67 }
68
69 pub fn can_apply(&self, event: &EventName) -> bool {
71 self.transitions
72 .iter()
73 .any(|transition| transition.from == self.current && &transition.event == event)
74 }
75
76 pub fn apply(&mut self, event: &EventName) -> RuntimeResult<&StateName> {
78 let transition = self
79 .transitions
80 .iter()
81 .find(|transition| transition.from == self.current && &transition.event == event);
82
83 let Some(transition) = transition else {
84 return Err(RuntimeError::InvalidStateTransition);
85 };
86
87 self.current = transition.to.clone();
88 Ok(&self.current)
89 }
90
91 pub fn transitions(&self) -> &[StateTransition] {
93 &self.transitions
94 }
95}
96
97#[derive(Debug, Default)]
99pub struct StateRegistry {
100 names: NameRegistry<StateName>,
101}
102
103impl StateRegistry {
104 pub fn new() -> Self {
106 Self::default()
107 }
108
109 pub fn register(&mut self, name: StateName) -> RuntimeResult<()> {
111 self.names.register(name, "state")
112 }
113
114 pub fn contains(&self, name: &StateName) -> bool {
116 self.names.contains(name)
117 }
118
119 pub fn len(&self) -> usize {
121 self.names.len()
122 }
123
124 pub fn is_empty(&self) -> bool {
126 self.names.is_empty()
127 }
128
129 pub fn list(&self) -> &[StateName] {
131 self.names.list()
132 }
133}
134
135#[cfg(test)]
136#[path = "state_tests.rs"]
137mod tests;