1use compact_str::CompactString;
2use serde::{Deserialize, Serialize};
3
4use crate::model::state::StateKind;
5use crate::model::{State, Statechart};
6
7#[derive(Debug, Clone, Serialize, Deserialize)]
12#[cfg_attr(
13 feature = "rkyv",
14 derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)
15)]
16pub struct FlatState {
17 pub id: CompactString,
19 pub kind: StateKind,
21 pub parent: Option<CompactString>,
23 pub initial: bool,
25 pub depth: u32,
27}
28
29#[derive(Debug, Clone, Serialize, Deserialize)]
31#[cfg_attr(
32 feature = "rkyv",
33 derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)
34)]
35pub struct FlatTransition {
36 pub source: CompactString,
38 pub target: CompactString,
40 pub event: Option<CompactString>,
42 pub guard: Option<CompactString>,
44}
45
46pub fn flatten(chart: &Statechart) -> (Vec<FlatState>, Vec<FlatTransition>) {
68 let (state_count, trans_count) = {
69 let mut sc = 0;
70 let mut tc = 0;
71 for s in chart.iter_all_states() {
72 sc += 1;
73 tc += s.transitions.len();
74 }
75 (sc, tc)
76 };
77 let mut states = Vec::with_capacity(state_count);
78 let mut transitions = Vec::with_capacity(trans_count);
79
80 let limit = crate::max_depth();
81 for state in &chart.states {
82 flatten_state(
83 state,
84 None,
85 0,
86 &chart.initial,
87 &mut states,
88 &mut transitions,
89 limit,
90 );
91 }
92
93 (states, transitions)
94}
95
96fn flatten_state(
97 state: &State,
98 parent: Option<&CompactString>,
99 depth: u32,
100 chart_initial: &CompactString,
101 states: &mut Vec<FlatState>,
102 transitions: &mut Vec<FlatTransition>,
103 limit: usize,
104) {
105 if depth as usize > limit {
106 return;
107 }
108 let is_initial = state.id == *chart_initial;
109
110 states.push(FlatState {
111 id: state.id.clone(),
112 kind: state.kind,
113 parent: parent.cloned(),
114 initial: is_initial,
115 depth,
116 });
117
118 for t in &state.transitions {
120 for target in &t.targets {
121 transitions.push(FlatTransition {
122 source: state.id.clone(),
123 target: target.clone(),
124 event: t.event.clone(),
125 guard: t.guard.clone(),
126 });
127 }
128 }
129
130 for child in &state.children {
132 flatten_state(
133 child,
134 Some(&state.id),
135 depth + 1,
136 chart_initial,
137 states,
138 transitions,
139 limit,
140 );
141 }
142}
143
144#[cfg(test)]
145mod tests {
146 use super::*;
147 use crate::model::{State, Transition};
148
149 #[test]
150 fn flatten_simple_chart() {
151 let chart = Statechart::new(
152 "a",
153 vec![
154 {
155 let mut s = State::atomic("a");
156 s.transitions.push(Transition::new("go", "b"));
157 s
158 },
159 {
160 let mut s = State::atomic("b");
161 s.transitions.push(Transition::new("done", "end"));
162 s
163 },
164 State::final_state("end"),
165 ],
166 );
167
168 let (states, transitions) = flatten(&chart);
169 assert_eq!(states.len(), 3);
170 assert_eq!(transitions.len(), 2);
171 assert!(states[0].initial);
172 assert_eq!(states[0].depth, 0);
173 }
174
175 #[test]
176 fn flatten_nested_chart() {
177 let chart = Statechart::new(
178 "main",
179 vec![State::compound(
180 "main",
181 "child_a",
182 vec![
183 {
184 let mut s = State::atomic("child_a");
185 s.transitions.push(Transition::new("next", "child_b"));
186 s
187 },
188 State::atomic("child_b"),
189 ],
190 )],
191 );
192
193 let (states, _) = flatten(&chart);
194 assert_eq!(states.len(), 3); assert_eq!(states[0].depth, 0);
196 assert_eq!(states[1].depth, 1);
197 assert_eq!(states[1].parent.as_deref(), Some("main"));
198 }
199}