1use alloc::vec::Vec;
28
29use crate::bitset::StateSet;
30use crate::compile::{CompiledChart, Domain, ROOT, StateIdx, TransIdx};
31use crate::ir::StateKind;
32
33#[derive(Clone, Debug, Default, PartialEq, Eq)]
36#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
37pub struct History {
38 values: Vec<Option<StateSet>>,
39}
40
41impl History {
42 pub fn new(n: usize) -> Self {
44 History {
45 values: alloc::vec![None; n],
46 }
47 }
48
49 pub fn get(&self, h: StateIdx) -> Option<&StateSet> {
51 self.values.get(h).and_then(|v| v.as_ref())
52 }
53
54 pub fn set(&mut self, h: StateIdx, value: StateSet) {
56 if h >= self.values.len() {
57 self.values.resize(h + 1, None);
58 }
59 self.values[h] = Some(value);
60 }
61}
62
63pub fn descriptor_matches(descriptor: &str, name: &str) -> bool {
68 let d = descriptor.trim();
69 if d == "*" {
70 return true;
71 }
72 let d = d.strip_suffix(".*").unwrap_or(d);
73 let d = d.strip_suffix('.').unwrap_or(d);
74 if d.is_empty() {
75 return false;
76 }
77 if name == d {
78 return true;
79 }
80 name.len() > d.len() && name.starts_with(d) && name.as_bytes()[d.len()] == b'.'
81}
82
83pub fn name_match(descriptors: &[alloc::string::String], name: &str) -> bool {
85 descriptors.iter().any(|d| descriptor_matches(d, name))
86}
87
88pub fn is_atomic(chart: &CompiledChart, s: StateIdx) -> bool {
90 s != ROOT
91 && chart.state(s).children.is_empty()
92 && !matches!(chart.state(s).kind, StateKind::History(_))
93}
94
95pub fn proper_ancestors(
98 chart: &CompiledChart,
99 s: StateIdx,
100 stop: Option<StateIdx>,
101) -> Vec<StateIdx> {
102 let mut out = Vec::new();
103 let mut cur = chart.state(s).parent;
104 while let Some(p) = cur {
105 if Some(p) == stop {
106 break;
107 }
108 out.push(p);
109 cur = chart.state(p).parent;
110 }
111 out
112}
113
114fn push_unique(v: &mut Vec<StateIdx>, s: StateIdx) {
115 if !v.contains(&s) {
116 v.push(s);
117 }
118}
119
120pub fn effective_targets(chart: &CompiledChart, history: &History, t: TransIdx) -> Vec<StateIdx> {
123 effective_targets_within(chart, history, t, chart.len())
124}
125
126fn effective_targets_within(
130 chart: &CompiledChart,
131 history: &History,
132 t: TransIdx,
133 fuel: usize,
134) -> Vec<StateIdx> {
135 let mut out = Vec::new();
136 for &s in &chart.transition(t).targets {
137 if matches!(chart.state(s).kind, StateKind::History(_)) {
138 match history.get(s) {
139 Some(v) => {
140 for x in v.to_vec() {
141 push_unique(&mut out, x);
142 }
143 }
144 None => {
145 if let Some(&dt) = chart.state(s).transitions.first()
146 && fuel > 0
147 {
148 for x in effective_targets_within(chart, history, dt, fuel - 1) {
149 push_unique(&mut out, x);
150 }
151 }
152 }
153 }
154 } else {
155 push_unique(&mut out, s);
156 }
157 }
158 out
159}
160
161pub fn find_lcca(chart: &CompiledChart, head: StateIdx, tail: &[StateIdx]) -> StateIdx {
164 for anc in proper_ancestors(chart, head, None) {
165 if chart.state(anc).kind == StateKind::Compound
166 && tail.iter().all(|s| chart.is_descendant(*s, anc))
167 {
168 return anc;
169 }
170 }
171 ROOT
172}
173
174pub fn compute_domain(chart: &CompiledChart, history: &History, t: TransIdx) -> Option<StateIdx> {
176 let tstates = effective_targets(chart, history, t);
177 if tstates.is_empty() {
178 return None;
179 }
180 let tr = chart.transition(t);
181 if tr.internal
182 && chart.state(tr.source).kind == StateKind::Compound
183 && tstates.iter().all(|s| chart.is_descendant(*s, tr.source))
184 {
185 return Some(tr.source);
186 }
187 Some(find_lcca(chart, tr.source, &tstates))
188}
189
190pub fn transition_domain(
193 chart: &CompiledChart,
194 history: &History,
195 t: TransIdx,
196) -> Option<StateIdx> {
197 match chart.transition(t).domain {
198 Domain::Targetless => None,
199 Domain::Static(d) => Some(d),
200 Domain::Dynamic => compute_domain(chart, history, t),
201 }
202}
203
204pub fn compute_exit_set(
207 chart: &CompiledChart,
208 config: &StateSet,
209 history: &History,
210 ts: &[TransIdx],
211) -> StateSet {
212 let mut out = chart.empty_set();
213 for &t in ts {
214 if chart.transition(t).targets.is_empty() {
215 continue;
216 }
217 if let Some(d) = transition_domain(chart, history, t) {
218 for s in config.to_vec() {
219 if chart.is_descendant(s, d) {
220 out.insert(s);
221 }
222 }
223 }
224 }
225 out
226}
227
228pub fn remove_conflicting_transitions(
232 chart: &CompiledChart,
233 config: &StateSet,
234 history: &History,
235 enabled: &[TransIdx],
236) -> Vec<TransIdx> {
237 let mut filtered: Vec<TransIdx> = Vec::new();
238 for &t1 in enabled {
239 let ex1 = compute_exit_set(chart, config, history, &[t1]);
240 let mut preempted = false;
241 let mut to_remove: Vec<TransIdx> = Vec::new();
242 for &t2 in &filtered {
243 let ex2 = compute_exit_set(chart, config, history, &[t2]);
244 if ex1.intersects(&ex2) {
245 if chart.is_descendant(chart.transition(t1).source, chart.transition(t2).source) {
246 to_remove.push(t2);
247 } else {
248 preempted = true;
249 break;
250 }
251 }
252 }
253 if !preempted {
254 filtered.retain(|t| !to_remove.contains(t));
255 filtered.push(t1);
256 }
257 }
258 filtered
259}
260
261pub fn select_transitions<F: FnMut(TransIdx) -> bool>(
268 chart: &CompiledChart,
269 config: &StateSet,
270 history: &History,
271 event: Option<&str>,
272 mut cond: F,
273) -> Vec<TransIdx> {
274 let mut enabled: Vec<TransIdx> = Vec::new();
275 for state in config.to_vec() {
276 if !is_atomic(chart, state) {
277 continue;
278 }
279 let mut chain = alloc::vec![state];
280 chain.extend(proper_ancestors(chart, state, None));
281 'outer: for s in chain {
282 for &t in &chart.state(s).transitions {
283 let tr = chart.transition(t);
284 let matches = match event {
285 None => tr.events.is_empty(),
286 Some(name) => !tr.events.is_empty() && name_match(&tr.events, name),
287 };
288 if matches && (tr.cond.is_none() || cond(t)) {
289 if !enabled.contains(&t) {
290 enabled.push(t);
291 }
292 break 'outer;
293 }
294 }
295 }
296 }
297 remove_conflicting_transitions(chart, config, history, &enabled)
298}
299
300pub fn record_history(
303 chart: &CompiledChart,
304 config: &StateSet,
305 exit_set: &StateSet,
306 history: &mut History,
307) {
308 for s in exit_set.to_vec() {
309 for &h in &chart.state(s).history {
310 let deep = matches!(
311 chart.state(h).kind,
312 StateKind::History(crate::ir::HistoryType::Deep)
313 );
314 let mut v = chart.empty_set();
315 for s0 in config.to_vec() {
316 let keep = if deep {
317 is_atomic(chart, s0) && chart.is_descendant(s0, s)
318 } else {
319 chart.state(s0).parent == Some(s)
320 };
321 if keep {
322 v.insert(s0);
323 }
324 }
325 history.set(h, v);
326 }
327 }
328}
329
330#[derive(Clone, Debug, PartialEq, Eq)]
332pub struct EntryPlan {
333 pub states: StateSet,
335 pub default_entry: StateSet,
337 pub default_history_content: Vec<(StateIdx, TransIdx)>,
340}
341
342pub fn transition_domains(
344 chart: &CompiledChart,
345 history: &History,
346 ts: &[TransIdx],
347) -> Vec<Option<StateIdx>> {
348 ts.iter()
349 .map(|&t| transition_domain(chart, history, t))
350 .collect()
351}
352
353pub fn compute_entry_set(
360 chart: &CompiledChart,
361 history: &History,
362 ts: &[TransIdx],
363 domains: &[Option<StateIdx>],
364) -> EntryPlan {
365 let mut p = EntryPlan {
366 states: chart.empty_set(),
367 default_entry: chart.empty_set(),
368 default_history_content: Vec::new(),
369 };
370 for (i, &t) in ts.iter().enumerate() {
371 let ancestor = domains.get(i).copied().flatten();
372 let floor = ancestor.unwrap_or(ROOT);
373 for &s in &chart.transition(t).targets {
374 add_descendants(chart, history, s, &mut p, chart.len(), floor);
375 }
376 for s in effective_targets(chart, history, t) {
377 add_ancestors(chart, history, s, ancestor, &mut p, chart.len(), floor);
378 }
379 }
380 p
381}
382
383fn add_descendants(
388 chart: &CompiledChart,
389 history: &History,
390 state: StateIdx,
391 p: &mut EntryPlan,
392 fuel: usize,
393 floor: StateIdx,
394) {
395 let st = chart.state(state);
396 if matches!(st.kind, StateKind::History(_)) {
397 let parent = st.parent.unwrap_or(ROOT);
398 match history.get(state) {
399 Some(v) => {
400 let v = v.to_vec();
401 for &s in &v {
402 add_descendants(chart, history, s, p, fuel, floor);
403 }
404 for &s in &v {
405 add_ancestors(chart, history, s, Some(parent), p, fuel, floor);
406 }
407 }
408 None => {
409 if let Some(&dt) = st.transitions.first()
410 && fuel > 0
411 {
412 p.default_history_content.retain(|(par, _)| *par != parent);
413 p.default_history_content.push((parent, dt));
414 for &s in &chart.transition(dt).targets {
415 add_descendants(chart, history, s, p, fuel - 1, floor);
416 }
417 for &s in &chart.transition(dt).targets {
418 add_ancestors(chart, history, s, Some(parent), p, fuel - 1, floor);
419 }
420 }
421 }
422 }
423 return;
424 }
425 p.states.insert(state);
426 match st.kind {
427 StateKind::Compound => {
428 p.default_entry.insert(state);
429 if let Some(it) = st.initial {
430 for &s in &chart.transition(it).targets {
431 add_descendants(chart, history, s, p, fuel, floor);
432 }
433 for &s in &chart.transition(it).targets {
434 add_ancestors(chart, history, s, Some(state), p, fuel, floor);
435 }
436 }
437 }
438 StateKind::Parallel => {
439 for &child in &st.children {
440 if !p.states.any_in_range(child + 1, chart.state(child).last) {
441 add_descendants(chart, history, child, p, fuel, floor);
442 }
443 }
444 }
445 _ => {}
446 }
447}
448
449fn add_ancestors(
457 chart: &CompiledChart,
458 history: &History,
459 state: StateIdx,
460 ancestor: Option<StateIdx>,
461 p: &mut EntryPlan,
462 fuel: usize,
463 floor: StateIdx,
464) {
465 for anc in proper_ancestors(chart, state, ancestor) {
466 if anc == ROOT || !chart.is_descendant(anc, floor) {
467 continue;
468 }
469 p.states.insert(anc);
470 if chart.state(anc).kind == StateKind::Parallel {
471 for &child in &chart.state(anc).children {
472 if !p.states.any_in_range(child + 1, chart.state(child).last) {
473 add_descendants(chart, history, child, p, fuel, floor);
474 }
475 }
476 }
477 }
478}
479
480pub fn is_in_final_state(chart: &CompiledChart, config: &StateSet, s: StateIdx) -> bool {
483 let st = chart.state(s);
484 match st.kind {
485 StateKind::Compound => st
486 .children
487 .iter()
488 .any(|c| chart.state(*c).kind == StateKind::Final && config.contains(*c)),
489 StateKind::Parallel => st
490 .children
491 .iter()
492 .all(|c| is_in_final_state(chart, config, *c)),
493 _ => false,
494 }
495}