1use crate::cel::{self, CelError, Env};
7use crate::machine::{HistoryKind, Machine, NodeId, StateDef, StateKind};
8use crate::model::Action;
9use crate::value::{map1, Value};
10use std::collections::{BTreeMap, BTreeSet, HashSet, VecDeque};
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub enum Status {
14 Active,
15 Faulted,
16 Terminated,
17}
18
19#[derive(Debug, Clone, Copy, PartialEq, Eq)]
20pub enum Mode {
21 Auto,
22 Manual,
23}
24
25pub type InstanceId = String;
26
27#[derive(Debug, Clone, PartialEq)]
28pub struct QueuedEvent {
29 pub etype: String,
30 pub payload: Value,
31 pub origin: Option<String>,
32}
33
34#[derive(Debug, Clone, PartialEq)]
35enum QItem {
36 Event(QueuedEvent),
37 Timer { state: NodeId, after_idx: usize },
38}
39
40impl QItem {
41 fn label(&self) -> String {
42 match self {
43 QItem::Event(e) => e.etype.clone(),
44 QItem::Timer { .. } => "__time__".into(),
45 }
46 }
47}
48
49#[derive(Debug, Clone)]
50pub struct ArmedTimer {
51 pub state: NodeId,
52 pub after_idx: usize,
53 pub due: u64,
54}
55
56#[derive(Debug, Clone)]
57pub struct DeadLetterRecord {
58 pub event: QueuedEvent,
59 pub reason: String,
60}
61
62#[derive(Debug, Clone)]
63enum HistRecord {
64 Shallow(Vec<NodeId>),
65 Deep(Vec<NodeId>),
66}
67
68pub struct Instance {
69 pub id: InstanceId,
70 pub parent: Option<InstanceId>,
71 pub def_id: String,
72 pub def_version: i64,
73 pub status: Status,
74 pub mode: Mode,
75 pub active: BTreeSet<NodeId>,
76 pub esvs: BTreeMap<String, Value>,
77 pub external_source: BTreeMap<String, Value>,
78 queue: VecDeque<QItem>,
79 deferred: Vec<QItem>,
80 pub timers: Vec<ArmedTimer>,
81 pub spawn_counter: u64,
82 pub dead_letter: Vec<DeadLetterRecord>,
83 history: BTreeMap<NodeId, HistRecord>,
84 done_emitted: HashSet<NodeId>,
85}
86
87#[derive(Debug, Clone, Default)]
89pub struct StepRecord {
90 pub event: String,
91 pub transition: Option<String>,
92 pub entered: Vec<String>,
93 pub exited: Vec<String>,
94 pub published: Vec<String>,
95 pub spawned: Vec<String>,
96 pub faulted: bool,
97}
98
99#[derive(Debug, Clone, Default)]
100pub struct RunResult {
101 pub published: Vec<String>,
102 pub spawned: Vec<String>,
103}
104
105fn ancestors_inclusive(m: &Machine, n: NodeId) -> Vec<NodeId> {
108 let mut out = Vec::new();
109 let mut cur = Some(n);
110 while let Some(c) = cur {
111 out.push(c);
112 cur = m.get(c).parent;
113 }
114 out
115}
116
117fn proper_ancestors(m: &Machine, n: NodeId) -> Vec<NodeId> {
118 let mut out = Vec::new();
119 let mut cur = m.get(n).parent;
120 while let Some(c) = cur {
121 out.push(c);
122 cur = m.get(c).parent;
123 }
124 out
125}
126
127fn is_ancestor_or_equal(m: &Machine, anc: NodeId, n: NodeId) -> bool {
128 let mut cur = Some(n);
129 while let Some(c) = cur {
130 if c == anc {
131 return true;
132 }
133 cur = m.get(c).parent;
134 }
135 false
136}
137
138fn is_strictly_below(m: &Machine, anc: NodeId, n: NodeId) -> bool {
139 n != anc && is_ancestor_or_equal(m, anc, n)
140}
141
142fn is_leaf(m: &Machine, n: NodeId) -> bool {
143 matches!(m.get(n).kind, StateKind::Simple | StateKind::Final)
144}
145
146fn active_leaves(inst: &Instance, m: &Machine) -> Vec<NodeId> {
147 inst.active
148 .iter()
149 .copied()
150 .filter(|&n| is_leaf(m, n))
151 .collect()
152}
153
154fn esv_key(state: &StateDef, name: &str) -> String {
155 format!("{}::{}", state.path, name)
156}
157
158fn nearest_declaring(inst: &Instance, m: &Machine, scope: NodeId, name: &str) -> Option<NodeId> {
159 for s in scope_chain(m, scope) {
160 if inst.active.contains(&s) && m.get(s).esvs.iter().any(|(n, _)| n == name) {
161 return Some(s);
162 }
163 }
164 None
165}
166
167fn resolve_visible(inst: &Instance, m: &Machine, scope: NodeId) -> BTreeMap<String, Value> {
168 let mut out = BTreeMap::new();
169 for s in scope_chain(m, scope) {
170 if !inst.active.contains(&s) {
171 continue;
172 }
173 let sd = m.get(s);
174 for (name, _) in &sd.esvs {
175 if out.contains_key(name) {
176 continue;
177 }
178 if let Some(v) = inst.esvs.get(&esv_key(sd, name)) {
179 out.insert(name.clone(), v.clone());
180 }
181 }
182 }
183 out
184}
185
186fn scope_chain(m: &Machine, scope: NodeId) -> Vec<NodeId> {
190 let mut chain = vec![scope];
191 let mut cur = scope;
192 loop {
193 if m.get(cur).is_sm_boundary {
194 break;
195 }
196 match m.get(cur).parent {
197 Some(p) => {
198 chain.push(p);
199 cur = p;
200 if m.get(cur).is_sm_boundary {
201 break;
202 }
203 }
204 None => break,
205 }
206 }
207 chain
208}
209
210struct SpawnReq {
213 parent: InstanceId,
214 scope: NodeId,
215 def_id: String,
216 payload: BTreeMap<String, Value>,
217 result_var: Option<String>,
218}
219
220#[derive(Default)]
221struct StepBuf {
222 deliveries: Vec<(InstanceId, QueuedEvent)>,
223 undirected: Vec<(InstanceId, String, Value)>,
224 spawns: Vec<SpawnReq>,
225 published: Vec<String>,
226 spawned: Vec<String>,
227 spawned_def: Vec<String>,
228 stop: bool,
229}
230
231pub struct Engine {
234 pub defs: BTreeMap<(String, i64), Machine>,
235 pub latest: BTreeMap<String, i64>,
236 pub instances: BTreeMap<InstanceId, Instance>,
237 pub clock: u64,
238 pub mode: Mode,
239 pub observer: Option<Box<dyn FnMut(&StepRecord) + Send>>,
240 run_published: Vec<String>,
241 run_spawned: Vec<String>,
242}
243
244impl Default for Engine {
245 fn default() -> Self {
246 Self::new()
247 }
248}
249
250#[derive(Debug)]
251pub enum EngineError {
252 NotFound(String),
253 Validation(String),
254 Faulted(String),
255 Other(String),
256}
257
258#[derive(Debug, Clone)]
259pub struct StateView {
260 pub instance: InstanceId,
261 pub def: String,
262 pub status: Status,
263 pub config: Vec<String>,
264 pub esvs: BTreeMap<String, Value>,
265}
266
267#[derive(Debug, Clone)]
268pub struct ListView {
269 pub id: InstanceId,
270 pub def: String,
271 pub parent: Option<InstanceId>,
272 pub status: Status,
273 pub config: Vec<String>,
274}
275
276#[derive(Debug, Clone)]
277pub struct InspectView {
278 pub instance: InstanceId,
279 pub status: Status,
280 pub config: Vec<String>,
281 pub esvs: BTreeMap<String, Value>,
282 pub enabled: Vec<String>,
283 pub queue: Vec<QueuedEvent>,
284 pub deferred: Vec<QueuedEvent>,
285 pub timers: Vec<ArmedTimer>,
286 pub history: BTreeMap<String, String>,
287 pub dead_letter: Vec<DeadLetterRecord>,
288}
289
290impl Engine {
291 pub fn new() -> Self {
292 Engine {
293 defs: BTreeMap::new(),
294 latest: BTreeMap::new(),
295 instances: BTreeMap::new(),
296 clock: 0,
297 mode: Mode::Auto,
298 observer: None,
299 run_published: Vec::new(),
300 run_spawned: Vec::new(),
301 }
302 }
303
304 pub fn set_observer<F: FnMut(&StepRecord) + Send + 'static>(&mut self, f: F) {
305 self.observer = Some(Box::new(f));
306 }
307
308 pub fn register(&mut self, m: Machine) {
309 let id = m.id.clone();
310 let ver = m.version;
311 self.defs.insert((id.clone(), ver), m);
312 let e = self.latest.entry(id).or_insert(0);
313 if ver > *e {
314 *e = ver;
315 }
316 }
317
318 fn def_ref(&self, id: &str, version: i64) -> &Machine {
319 self.defs
320 .get(&(id.to_string(), version))
321 .expect("definition registered")
322 }
323
324 pub fn def(&self, id: &str, version: Option<i64>) -> Result<&Machine, EngineError> {
325 let v = match version {
326 Some(v) => v,
327 None => *self
328 .latest
329 .get(id)
330 .ok_or_else(|| EngineError::NotFound(format!("definition '{id}'")))?,
331 };
332 self.defs
333 .get(&(id.to_string(), v))
334 .ok_or_else(|| EngineError::NotFound(format!("definition '{id}@{v}'")))
335 }
336
337 pub fn create_root(
338 &mut self,
339 id: &str,
340 def_id: &str,
341 version: Option<i64>,
342 external: &BTreeMap<String, Value>,
343 ) -> Result<(), EngineError> {
344 if self.instances.contains_key(id) {
345 return Err(EngineError::Other(format!("instance '{id}' already exists")));
346 }
347 let (did, ver) = {
348 let m = self.def(def_id, version)?;
349 (m.id.clone(), m.version)
350 };
351 let mut inst = new_instance(id, None, did, ver, self.mode, external);
352 let m = self.def_ref(&inst.def_id, inst.def_version).clone();
353 let mut buf = StepBuf::default();
354 let mut rec = StepRecord::default();
355 enter(&mut inst, &m, m.top, self.clock, &Value::Null, None, &mut buf, &mut rec);
356 descend(&mut inst, &m, m.top, self.clock, &Value::Null, None, &mut buf, &mut rec);
357 self.instances.insert(id.to_string(), inst);
358 self.commit(buf)?;
359 self.run_to_quiescence()?;
360 Ok(())
361 }
362
363 pub fn instance(&self, id: &str) -> Result<&Instance, EngineError> {
364 self.instances
365 .get(id)
366 .ok_or_else(|| EngineError::NotFound(format!("instance '{id}'")))
367 }
368
369 pub fn validate_event(&self, inst_id: &str, etype: &str, payload: &Value) -> Result<(), String> {
370 let inst = self
371 .instances
372 .get(inst_id)
373 .ok_or_else(|| format!("instance '{inst_id}' not found"))?;
374 let m = self.def_ref(&inst.def_id, inst.def_version);
375 validate_event_payload(m, etype, payload)
376 }
377
378 pub fn inject(&mut self, inst_id: &str, etype: &str, payload: Value) -> Result<bool, EngineError> {
379 let ok = {
380 let inst = self.instances.get(inst_id).ok_or_else(|| {
381 EngineError::NotFound(format!("instance '{inst_id}'"))
382 })?;
383 let m = self.def_ref(&inst.def_id, inst.def_version);
384 validate_event_payload(m, etype, &payload).is_ok()
385 };
386 if ok {
387 let inst = self.instances.get_mut(inst_id).unwrap();
388 inst.queue.push_back(QItem::Event(QueuedEvent {
389 etype: etype.to_string(),
390 payload,
391 origin: None,
392 }));
393 }
394 Ok(ok)
395 }
396
397 pub fn send(&mut self, inst_id: &str, etype: &str, payload: Value) -> Result<RunResult, EngineError> {
398 self.run_published.clear();
399 self.run_spawned.clear();
400 let accepted = self.inject(inst_id, etype, payload)?;
401 if accepted && self.mode == Mode::Auto {
402 self.run_to_quiescence()?;
403 }
404 Ok(RunResult {
405 published: std::mem::take(&mut self.run_published),
406 spawned: std::mem::take(&mut self.run_spawned),
407 })
408 }
409
410 pub fn advance(&mut self, duration_ms: u64) -> Result<RunResult, EngineError> {
411 self.run_published.clear();
412 self.run_spawned.clear();
413 self.clock = self.clock.saturating_add(duration_ms);
414 loop {
415 let pick: Option<(InstanceId, usize, u64)> = {
417 let mut best: Option<(InstanceId, usize, u64)> = None;
418 for (iid, inst) in &self.instances {
419 for (i, t) in inst.timers.iter().enumerate() {
420 if t.due <= self.clock {
421 let cand = (iid.clone(), i, t.due);
422 match &best {
423 None => best = Some(cand),
424 Some(b) => {
425 if (t.due, &iid.clone()) < (b.2, &b.0) {
426 best = Some(cand);
427 }
428 }
429 }
430 }
431 }
432 }
433 best
434 };
435 match pick {
436 None => break,
437 Some((iid, i, _)) => {
438 let inst = self.instances.get_mut(&iid).unwrap();
439 let timer = inst.timers.remove(i);
440 inst.queue.push_back(QItem::Timer {
441 state: timer.state,
442 after_idx: timer.after_idx,
443 });
444 }
445 }
446 }
447 self.run_to_quiescence()?;
448 Ok(RunResult {
449 published: std::mem::take(&mut self.run_published),
450 spawned: std::mem::take(&mut self.run_spawned),
451 })
452 }
453
454 pub fn step(&mut self, inst_id: &str, n: usize) -> Result<Vec<StepRecord>, EngineError> {
455 self.run_published.clear();
456 self.run_spawned.clear();
457 let mut records = Vec::new();
458 for _ in 0..n {
459 let has = self
460 .instances
461 .get(inst_id)
462 .map(|i| !i.queue.is_empty())
463 .unwrap_or(false);
464 if !has {
465 break;
466 }
467 let rec = self.dispatch_one(inst_id)?;
468 records.push(rec);
469 self.run_to_quiescence()?;
470 }
471 Ok(records)
472 }
473
474 pub fn env_change(
475 &mut self,
476 inst_id: &str,
477 changed: BTreeMap<String, Value>,
478 ) -> Result<RunResult, EngineError> {
479 let mut payload = BTreeMap::new();
480 payload.insert("changed".to_string(), Value::Map(changed));
481 self.send(inst_id, "env", Value::Map(payload))
482 }
483
484 pub fn set_mode(&mut self, mode: Mode) {
485 self.mode = mode;
486 for inst in self.instances.values_mut() {
487 inst.mode = mode;
488 }
489 }
490 pub fn get_mode(&self) -> Mode {
491 self.mode
492 }
493
494 pub fn migrate_quiescent(&mut self) -> Result<(), EngineError> {
496 let candidates: Vec<(InstanceId, String, i64)> = self
498 .instances
499 .iter()
500 .filter(|(_, i)| i.status == Status::Active && i.queue.is_empty() && i.deferred.is_empty())
501 .map(|(id, i)| (id.clone(), i.def_id.clone(), i.def_version))
502 .collect();
503 for (iid, def_id, v_old) in candidates {
504 if let Some(new_ver) = self.find_migration_target(&def_id, v_old) {
505 self.apply_migration(&iid, &def_id, v_old, new_ver)?;
506 }
507 }
508 Ok(())
509 }
510
511 fn find_migration_target(&self, def_id: &str, from: i64) -> Option<i64> {
512 let latest = *self.latest.get(def_id)?;
513 if latest <= from {
514 return None;
515 }
516 for v in (from + 1)..=latest {
518 if let Some(new_m) = self.defs.get(&(def_id.to_string(), v)) {
519 if new_m.migrations.iter().any(|mig| mig.from == from && mig.to == v) {
520 return Some(v);
521 }
522 }
523 }
524 None
525 }
526
527 fn apply_migration(
528 &mut self,
529 iid: &str,
530 def_id: &str,
531 from: i64,
532 to: i64,
533 ) -> Result<(), EngineError> {
534 let migration = {
535 let new_m = self.defs.get(&(def_id.to_string(), to)).cloned();
536 let old_m = self.defs.get(&(def_id.to_string(), from)).cloned();
537 let (new_m, old_m) = match (new_m, old_m) {
538 (Some(n), Some(o)) => (n, o),
539 _ => return Ok(()),
540 };
541 new_m
542 .migrations
543 .iter()
544 .find(|m| m.from == from && m.to == to)
545 .cloned()
546 .map(|m| (m, new_m, old_m))
547 };
548 let (migration, new_m, old_m) = match migration {
549 Some(x) => x,
550 None => return Ok(()),
551 };
552 let leaf_name = {
554 let inst = self.instances.get(iid).unwrap();
555 let leaves = active_leaves(inst, &old_m);
556 if leaves.len() != 1 {
557 return Ok(()); }
559 old_m.get(leaves[0]).id.clone()
560 };
561 if let Some(when) = &migration.when {
563 let _inst = self.instances.get(iid).unwrap();
564 let mut env = Env::new();
565 env = env.with("state", Value::Str(leaf_name.clone()));
566 if !cel::eval_bool(when, &env).unwrap_or(false) {
567 return Ok(());
568 }
569 }
570 let new_leaf_name = match migration.state_map.get(&leaf_name) {
572 Some(n) => n.clone(),
573 None => return Ok(()),
574 };
575 let new_leaf = match new_m.by_name.get(&new_leaf_name).copied() {
576 Some(n) => n,
577 None => return Ok(()),
578 };
579 let inst = self.instances.get_mut(iid).unwrap();
581 inst.active.clear();
582 for a in ancestors_inclusive(&new_m, new_leaf) {
583 inst.active.insert(a);
584 }
585 inst.def_version = to;
586 let mut buf = StepBuf::default();
588 let mut rec = StepRecord::default();
589 let event = Value::Null;
590 let scope = new_m.top;
592 let mut env = build_env(inst, &new_m, scope, &event);
593 env = env.with("state", Value::Str(leaf_name.clone()));
594 for a in &migration.esvs {
595 if let Action::Assign(pairs) = a {
597 for (name, expr) in pairs {
598 if let Ok(val) = cel::eval(expr, &env) {
599 if let Some(st) = nearest_declaring(inst, &new_m, scope, name) {
600 let decl = new_m
601 .get(st)
602 .esvs
603 .iter()
604 .find(|(n, _)| n == name)
605 .map(|(_, d)| d.clone())
606 .unwrap();
607 if let Some(c) = val.coerce_to(&decl.ty) {
608 inst.esvs.insert(esv_key(new_m.get(st), name), c);
609 }
610 }
611 }
612 }
613 } else {
614 let _ = run_one(inst, &new_m, scope, a, &event, None, &mut buf, &mut rec);
615 }
616 }
617 let _ = (buf, rec);
618 Ok(())
619 }
620
621 fn run_to_quiescence(&mut self) -> Result<(), EngineError> {
624 let mut guard = 0u32;
625 loop {
626 guard += 1;
627 if guard > 200_000 {
628 return Err(EngineError::Other("runaway quiescence loop".into()));
629 }
630 let target = self
631 .instances
632 .iter()
633 .filter(|(_, i)| i.status == Status::Active && i.mode == Mode::Auto && !i.queue.is_empty())
634 .map(|(id, _)| id.clone())
635 .next();
636 match target {
637 Some(id) => {
638 self.dispatch_one(&id)?;
639 }
640 None => break,
641 }
642 }
643 Ok(())
644 }
645
646 fn dispatch_one(&mut self, inst_id: &str) -> Result<StepRecord, EngineError> {
647 let (did, ver) = {
648 let inst = self.instances.get(inst_id).ok_or_else(|| {
649 EngineError::NotFound(format!("instance '{inst_id}'"))
650 })?;
651 (inst.def_id.clone(), inst.def_version)
652 };
653 let m = self.def_ref(&did, ver).clone();
654 let item = self.instances.get_mut(inst_id).unwrap().queue.pop_front();
655 let rec = match item {
656 None => StepRecord::default(),
657 Some(qi) => {
658 let rec = self.execute_rtc(inst_id, &m, qi);
659 if let Some(o) = &mut self.observer {
660 o(&rec);
661 }
662 rec
663 }
664 };
665 Ok(rec)
666 }
667
668 fn execute_rtc(&mut self, inst_id: &str, m: &Machine, item: QItem) -> StepRecord {
669 let mut rec = StepRecord {
670 event: item.label(),
671 ..Default::default()
672 };
673 let etype = match &item {
674 QItem::Event(e) => Some(e.etype.clone()),
675 QItem::Timer { .. } => None,
676 };
677 let event_value = match &item {
678 QItem::Event(e) => event_binding(e),
679 QItem::Timer { .. } => Value::Null,
680 };
681
682 let backup = self.instances.get(inst_id).map(|i| Instance::snapshot_state(i));
684
685 if etype.as_deref() == Some("env") {
687 if let Some(changed) = changed_of(&event_value) {
688 for (k, v) in changed {
689 self.instances
690 .get_mut(inst_id)
691 .unwrap()
692 .external_source
693 .insert(k, v);
694 }
695 }
696 }
697
698 let handlers = match &item {
699 QItem::Event(e) => find_handlers(self.instances.get_mut(inst_id).unwrap(), m, &e.etype, &event_value),
700 QItem::Timer { state, after_idx } => vec![Handler { state: *state, kind: HKind::Timer(*after_idx) }],
701 };
702
703 let mut buf = StepBuf::default();
704
705 if handlers.is_empty() {
706 let defer_set = effective_defer(self.instances.get(inst_id).unwrap(), m);
707 let do_defer = match &item {
708 QItem::Event(e) => defer_set.contains(&e.etype),
709 QItem::Timer { .. } => false,
710 };
711 if do_defer {
712 self.instances.get_mut(inst_id).unwrap().deferred.push(item);
713 }
714 return rec;
715 }
716
717 let mut faulted: Option<CelError> = None;
719 for h in &handlers {
720 let inst = self.instances.get_mut(inst_id).unwrap();
721 if let Err(e) = execute_handler(inst, m, h, &event_value, etype.as_deref(), &mut buf, &mut rec) {
722 faulted = Some(e);
723 break;
724 }
725 }
726
727 if let Some(fault) = faulted {
728 if let Some(b) = backup {
730 let inst = self.instances.get_mut(inst_id).unwrap();
731 inst.restore_state(b);
732 }
733 rec.faulted = true;
734 rec.published.clear();
735 rec.spawned.clear();
736 rec.entered.clear();
737 rec.exited.clear();
738 rec.transition = None;
739 if let QItem::Event(e) = &item {
740 self.instances.get_mut(inst_id).unwrap().dead_letter.push(DeadLetterRecord {
741 event: e.clone(),
742 reason: fault.to_string(),
743 });
744 }
745 let has_err = scope_has_error_handler(self.instances.get(inst_id).unwrap(), m);
746 if has_err {
747 self.instances.get_mut(inst_id).unwrap().queue.push_front(QItem::Event(QueuedEvent {
748 etype: "error".into(),
749 payload: Value::Map(error_payload(&fault)),
750 origin: None,
751 }));
752 } else {
753 self.instances.get_mut(inst_id).unwrap().status = Status::Faulted;
754 }
755 return rec;
756 }
757
758 let _ = self.commit(buf);
760
761 let m2 = m.clone();
763 self.post_step(inst_id, &m2);
764
765 if self.instances.get(inst_id).map(|i| i.status == Status::Active).unwrap_or(false) {
767 }
769
770 rec
771 }
772
773 fn post_step(&mut self, inst_id: &str, m: &Machine) {
774 let complete: Vec<NodeId> = self
778 .instances
779 .get(inst_id)
780 .map(|i| complete_states(i, m))
781 .unwrap_or_default();
782 for s in complete {
783 if s == m.top {
784 continue;
785 }
786 let already = self.instances.get(inst_id).unwrap().done_emitted.contains(&s);
787 if already {
788 continue;
789 }
790 let leaf_name = self
791 .instances
792 .get(inst_id)
793 .map(|i| composite_leaf_name(i, m, s))
794 .unwrap_or_default();
795 let inst = self.instances.get_mut(inst_id).unwrap();
796 inst.done_emitted.insert(s);
797 inst.queue.push_back(QItem::Event(QueuedEvent {
798 etype: "done".into(),
799 payload: map1("state", Value::Str(leaf_name)),
800 origin: None,
801 }));
802 }
803 self.handle_termination(inst_id, m);
805 let inst = self.instances.get_mut(inst_id).unwrap();
807 replay_deferred(inst, m);
808 }
809
810 fn handle_termination(&mut self, inst_id: &str, m: &Machine) {
811 let (terminate, parent_id) = {
812 let inst = match self.instances.get(inst_id) {
813 Some(i) if i.status == Status::Active => i,
814 _ => return,
815 };
816 let top_final = inst.active.iter().any(|&n| {
817 m.get(n).kind == StateKind::Final && m.get(n).parent == Some(m.top)
818 });
819 let stop_pending = inst
820 .queue
821 .iter()
822 .any(|q| matches!(q, QItem::Event(e) if e.etype == "__stop__"));
823 let do_term = (top_final && inst.parent.is_some()) || stop_pending;
824 (do_term, inst.parent.clone())
825 };
826 if terminate {
827 let inst = self.instances.get_mut(inst_id).unwrap();
828 inst.queue.retain(|q| !matches!(q, QItem::Event(e) if e.etype == "__stop__"));
830 let leaves: Vec<NodeId> = inst.active.iter().copied().filter(|&n| is_leaf(m, n)).collect();
832 let mut sink = StepBuf::default();
833 for leaf in leaves {
834 let mut rec = StepRecord::default();
835 exit_up_to(inst, m, leaf, Some(m.top), &mut sink, &mut rec);
836 }
837 inst.active.clear();
838 inst.status = Status::Terminated;
839 inst.done_emitted.clear();
840 let _ = sink;
843 if let Some(parent) = parent_id {
844 if let Some(p) = self.instances.get_mut(&parent) {
845 if p.status == Status::Active {
846 p.queue.push_back(QItem::Event(QueuedEvent {
847 etype: "done".into(),
848 payload: Value::Null,
849 origin: Some(inst_id.to_string()),
850 }));
851 }
852 }
853 }
854 }
855 }
856
857 fn commit(&mut self, buf: StepBuf) -> Result<(), EngineError> {
858 self.run_published.extend(buf.published);
859 self.run_spawned.extend(buf.spawned_def);
860
861 for (target, ev) in buf.deliveries {
863 if let Some(t) = self.instances.get_mut(&target) {
864 if t.status == Status::Active {
865 t.queue.push_back(QItem::Event(ev));
866 }
867 }
868 }
869 for (publisher, etype, payload) in buf.undirected {
871 let scope = self
872 .instances
873 .get(&publisher)
874 .and_then(|p| {
875 let m = self.defs.get(&(p.def_id.clone(), p.def_version))?;
876 m.events.get(&etype).map(|d| d.scope)
877 })
878 .unwrap_or(crate::machine::Scope::Internal);
879 let targets = self.undirected_targets(&publisher, &etype, scope);
880 for t in targets {
881 if let Some(ti) = self.instances.get_mut(&t) {
882 if ti.status == Status::Active {
883 ti.queue.push_back(QItem::Event(QueuedEvent {
884 etype: etype.clone(),
885 payload: payload.clone(),
886 origin: Some(publisher.clone()),
887 }));
888 }
889 }
890 }
891 }
892 for sp in buf.spawns {
894 let parent_id = sp.parent.clone();
895 let n = {
896 let p = self.instances.get_mut(&parent_id).unwrap();
897 p.spawn_counter += 1;
898 p.spawn_counter
899 };
900 let child_id = format!("{parent_id}/{n}");
901 if let Some(res) = &sp.result_var {
903 let m_clone = self
904 .defs
905 .get({
906 let p = self.instances.get(&parent_id).unwrap();
907 &(p.def_id.clone(), p.def_version)
908 })
909 .cloned();
910 if let Some(m) = m_clone {
911 let p = self.instances.get_mut(&parent_id).unwrap();
912 if let Some(st) = nearest_declaring(p, &m, sp.scope, res) {
913 p.esvs.insert(esv_key(m.get(st), res), Value::Str(child_id.clone()));
914 }
915 }
916 }
917 let child_m = match self.def(&sp.def_id, None) {
919 Ok(cm) => cm.clone(),
920 Err(_) => continue,
921 };
922 let mut child = new_instance(
923 &child_id,
924 Some(parent_id.clone()),
925 child_m.id.clone(),
926 child_m.version,
927 self.mode,
928 &BTreeMap::new(),
929 );
930 for (name, _) in &child_m.get(child_m.top).esvs.clone() {
932 if let Some(v) = sp.payload.get(name) {
933 child.external_source.insert(name.clone(), v.clone());
934 }
935 }
936 let mut cbuf = StepBuf::default();
937 let mut crec = StepRecord::default();
938 enter(&mut child, &child_m, child_m.top, self.clock, &Value::Null, None, &mut cbuf, &mut crec);
939 descend(&mut child, &child_m, child_m.top, self.clock, &Value::Null, None, &mut cbuf, &mut crec);
940 self.instances.insert(child_id, child);
941 self.commit(cbuf)?;
943 }
944 Ok(())
945 }
946
947 fn undirected_targets(
948 &self,
949 publisher: &str,
950 etype: &str,
951 scope: crate::machine::Scope,
952 ) -> Vec<InstanceId> {
953 use crate::machine::Scope;
954 let candidates: Vec<InstanceId> = match scope {
956 Scope::Internal => vec![publisher.to_string()],
957 Scope::Global => self.instances.keys().cloned().collect(),
958 Scope::Local => {
959 let mut set: HashSet<InstanceId> = HashSet::new();
961 let mut cur = Some(publisher.to_string());
963 while let Some(id) = cur {
964 set.insert(id.clone());
965 cur = self.instances.get(&id).and_then(|i| i.parent.clone());
966 }
967 let mut stack = vec![publisher.to_string()];
969 while let Some(id) = stack.pop() {
970 set.insert(id.clone());
971 for (other_id, other) in &self.instances {
972 if other.parent.as_deref() == Some(id.as_str()) {
973 stack.push(other_id.clone());
974 }
975 }
976 }
977 set.into_iter().collect()
978 }
979 };
980 candidates
981 .into_iter()
982 .filter(|cid| {
983 if scope == Scope::Internal && cid == publisher {
984 return true; }
986 let inst = match self.instances.get(cid) {
987 Some(i) => i,
988 None => return false,
989 };
990 let m = self.defs.get(&(inst.def_id.clone(), inst.def_version));
991 m.map(|mm| mm.subscribe.iter().any(|s| s == etype))
992 .unwrap_or(false)
993 })
994 .collect()
995 }
996
997 pub fn state_view(&self, inst_id: &str) -> Result<StateView, EngineError> {
1000 let inst = self.instance(inst_id)?;
1001 let m = self.def_ref(&inst.def_id, inst.def_version);
1002 Ok(StateView {
1003 instance: inst_id.to_string(),
1004 def: format!("{}@{}", inst.def_id, inst.def_version),
1005 status: inst.status,
1006 config: config_of(inst, m),
1007 esvs: reported_esvs(inst, m),
1008 })
1009 }
1010
1011 pub fn list_view(&self) -> Vec<ListView> {
1012 self.instances
1013 .iter()
1014 .map(|(id, inst)| {
1015 let m = self.def_ref(&inst.def_id, inst.def_version);
1016 ListView {
1017 id: id.clone(),
1018 def: format!("{}@{}", inst.def_id, inst.def_version),
1019 parent: inst.parent.clone(),
1020 status: inst.status,
1021 config: config_of(inst, m),
1022 }
1023 })
1024 .collect()
1025 }
1026
1027 pub fn inspect_view(&self, inst_id: &str) -> Result<InspectView, EngineError> {
1028 let inst = self.instance(inst_id)?;
1029 let m = self.def_ref(&inst.def_id, inst.def_version);
1030 let history = inst
1031 .history
1032 .iter()
1033 .map(|(n, h)| {
1034 let kind = match h {
1035 HistRecord::Shallow(_) => "shallow",
1036 HistRecord::Deep(_) => "deep",
1037 };
1038 (m.get(*n).path.clone(), kind.to_string())
1039 })
1040 .collect();
1041 Ok(InspectView {
1042 instance: inst_id.to_string(),
1043 status: inst.status,
1044 config: config_of(inst, m),
1045 esvs: reported_esvs(inst, m),
1046 enabled: enabled_events(inst, m),
1047 queue: inst.queue.iter().filter_map(|q| match q {
1048 QItem::Event(e) => Some(e.clone()),
1049 _ => None,
1050 }).collect(),
1051 deferred: inst.deferred.iter().filter_map(|q| match q {
1052 QItem::Event(e) => Some(e.clone()),
1053 _ => None,
1054 }).collect(),
1055 timers: inst.timers.clone(),
1056 history,
1057 dead_letter: inst.dead_letter.clone(),
1058 })
1059 }
1060
1061 pub fn enabled_events(&self, inst_id: &str) -> Result<Vec<String>, EngineError> {
1064 let inst = self.instance(inst_id)?;
1065 let m = self.def_ref(&inst.def_id, inst.def_version);
1066 Ok(enabled_events(inst, m))
1067 }
1068
1069 pub fn snapshot(&self, inst_id: &str) -> Result<Snapshot, EngineError> {
1070 let inst = self.instance(inst_id)?;
1071 let m = self.def_ref(&inst.def_id, inst.def_version);
1072 Snapshot::from_instance(inst, m)
1073 }
1074
1075 pub fn restore(&mut self, snap: Snapshot) -> Result<(), EngineError> {
1076 let m = self
1077 .defs
1078 .get(&(snap.def_id.clone(), snap.def_version))
1079 .ok_or_else(|| EngineError::NotFound(format!("definition '{}@{}'", snap.def_id, snap.def_version)))?
1080 .clone();
1081 let inst = snap.into_instance(&m);
1082 self.instances.insert(inst.id.clone(), inst);
1083 Ok(())
1084 }
1085}
1086
1087#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
1090pub struct Snapshot {
1091 pub def_id: String,
1092 pub def_version: i64,
1093 pub id: InstanceId,
1094 pub parent_id: Option<InstanceId>,
1095 pub status: String,
1096 pub state_config: Vec<String>,
1097 pub esvs: BTreeMap<String, Value>,
1098 pub external_source: BTreeMap<String, Value>,
1099 pub queue: Vec<SnapEvent>,
1100 pub deferred: Vec<SnapEvent>,
1101 pub timers: Vec<SnapTimer>,
1102 pub dead_letter: Vec<SnapDeadLetter>,
1103 pub history: BTreeMap<String, SnapHist>,
1104 pub spawn_counter: u64,
1105 pub mode: String,
1106 #[serde(default)]
1107 pub clock: u64,
1108}
1109
1110#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
1111pub struct SnapEvent {
1112 pub etype: String,
1113 pub payload: Value,
1114 pub origin: Option<String>,
1115}
1116#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
1117pub struct SnapTimer {
1118 pub state: String,
1119 pub after_idx: usize,
1120 pub due: u64,
1121}
1122#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
1123pub struct SnapDeadLetter {
1124 pub event: SnapEvent,
1125 pub reason: String,
1126}
1127#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
1128pub struct SnapHist {
1129 pub kind: String,
1130 pub states: Vec<String>,
1131}
1132
1133impl Snapshot {
1134 fn from_instance(inst: &Instance, m: &Machine) -> Result<Snapshot, EngineError> {
1135 let state_config: Vec<String> = active_leaves(inst, m)
1136 .into_iter()
1137 .map(|n| m.get(n).path.clone())
1138 .collect();
1139 let history = inst
1140 .history
1141 .iter()
1142 .map(|(n, h)| {
1143 let (kind, states) = match h {
1144 HistRecord::Shallow(s) => ("shallow", s.clone()),
1145 HistRecord::Deep(s) => ("deep", s.clone()),
1146 };
1147 (
1148 m.get(*n).path.clone(),
1149 SnapHist {
1150 kind: kind.to_string(),
1151 states: states.iter().map(|x| m.get(*x).path.clone()).collect(),
1152 },
1153 )
1154 })
1155 .collect();
1156 Ok(Snapshot {
1157 def_id: inst.def_id.clone(),
1158 def_version: inst.def_version,
1159 id: inst.id.clone(),
1160 parent_id: inst.parent.clone(),
1161 status: status_str(inst.status).to_string(),
1162 state_config,
1163 esvs: inst.esvs.clone(),
1164 external_source: inst.external_source.clone(),
1165 queue: snap_events(inst.queue.iter()),
1166 deferred: snap_events(inst.deferred.iter()),
1167 timers: inst
1168 .timers
1169 .iter()
1170 .map(|t| SnapTimer {
1171 state: m.get(t.state).path.clone(),
1172 after_idx: t.after_idx,
1173 due: t.due,
1174 })
1175 .collect(),
1176 dead_letter: inst
1177 .dead_letter
1178 .iter()
1179 .map(|d| SnapDeadLetter {
1180 event: SnapEvent {
1181 etype: d.event.etype.clone(),
1182 payload: d.event.payload.clone(),
1183 origin: d.event.origin.clone(),
1184 },
1185 reason: d.reason.clone(),
1186 })
1187 .collect(),
1188 history,
1189 spawn_counter: inst.spawn_counter,
1190 mode: match inst.mode {
1191 Mode::Auto => "auto",
1192 Mode::Manual => "manual",
1193 }
1194 .into(),
1195 clock: 0,
1196 })
1197 }
1198
1199 fn into_instance(self, m: &Machine) -> Instance {
1200 let status = match self.status.as_str() {
1201 "faulted" => Status::Faulted,
1202 "terminated" => Status::Terminated,
1203 _ => Status::Active,
1204 };
1205 let mode = match self.mode.as_str() {
1206 "manual" => Mode::Manual,
1207 _ => Mode::Auto,
1208 };
1209 let leaves: Vec<NodeId> = self
1210 .state_config
1211 .iter()
1212 .filter_map(|p| m.by_path.get(p).copied())
1213 .collect();
1214 let mut inst = Instance {
1215 id: self.id,
1216 parent: self.parent_id,
1217 def_id: self.def_id,
1218 def_version: self.def_version,
1219 status,
1220 mode,
1221 active: BTreeSet::new(),
1222 esvs: self.esvs,
1223 external_source: self.external_source,
1224 queue: self.queue.into_iter().map(|e| QItem::Event(QueuedEvent {
1225 etype: e.etype,
1226 payload: e.payload,
1227 origin: e.origin,
1228 })).collect(),
1229 deferred: self.deferred.into_iter().map(|e| QItem::Event(QueuedEvent {
1230 etype: e.etype,
1231 payload: e.payload,
1232 origin: e.origin,
1233 })).collect(),
1234 timers: self.timers.into_iter().filter_map(|t| {
1235 m.by_path.get(&t.state).copied().map(|state| ArmedTimer {
1236 state,
1237 after_idx: t.after_idx,
1238 due: t.due,
1239 })
1240 }).collect(),
1241 spawn_counter: self.spawn_counter,
1242 dead_letter: self.dead_letter.into_iter().map(|d| DeadLetterRecord {
1243 event: QueuedEvent {
1244 etype: d.event.etype,
1245 payload: d.event.payload,
1246 origin: d.event.origin,
1247 },
1248 reason: d.reason,
1249 }).collect(),
1250 history: self.history.into_iter().filter_map(|(path, h)| {
1251 let state = m.by_path.get(&path).copied()?;
1252 let states: Vec<NodeId> = h
1253 .states
1254 .iter()
1255 .filter_map(|p| m.by_path.get(p).copied())
1256 .collect();
1257 let rec = if h.kind == "shallow" {
1258 HistRecord::Shallow(states)
1259 } else {
1260 HistRecord::Deep(states)
1261 };
1262 Some((state, rec))
1263 }).collect(),
1264 done_emitted: HashSet::new(),
1265 };
1266 for leaf in leaves {
1267 for a in ancestors_inclusive(m, leaf) {
1268 inst.active.insert(a);
1269 }
1270 }
1271 inst
1272 }
1273}
1274
1275fn snap_events<'a>(items: impl Iterator<Item = &'a QItem>) -> Vec<SnapEvent> {
1276 items
1277 .filter_map(|q| match q {
1278 QItem::Event(e) => Some(SnapEvent {
1279 etype: e.etype.clone(),
1280 payload: e.payload.clone(),
1281 origin: e.origin.clone(),
1282 }),
1283 QItem::Timer { .. } => None,
1284 })
1285 .collect()
1286}
1287
1288fn status_str(s: Status) -> &'static str {
1289 match s {
1290 Status::Active => "active",
1291 Status::Faulted => "faulted",
1292 Status::Terminated => "terminated",
1293 }
1294}
1295
1296fn new_instance(
1299 id: &str,
1300 parent: Option<InstanceId>,
1301 def_id: String,
1302 def_version: i64,
1303 mode: Mode,
1304 external: &BTreeMap<String, Value>,
1305) -> Instance {
1306 Instance {
1307 id: id.to_string(),
1308 parent,
1309 def_id,
1310 def_version,
1311 status: Status::Active,
1312 mode,
1313 active: BTreeSet::new(),
1314 esvs: BTreeMap::new(),
1315 external_source: external.clone(),
1316 queue: VecDeque::new(),
1317 deferred: Vec::new(),
1318 timers: Vec::new(),
1319 spawn_counter: 0,
1320 dead_letter: Vec::new(),
1321 history: BTreeMap::new(),
1322 done_emitted: HashSet::new(),
1323 }
1324}
1325
1326impl Instance {
1327 fn snapshot_state(&self) -> InstanceState {
1328 InstanceState {
1329 active: self.active.clone(),
1330 esvs: self.esvs.clone(),
1331 timers: self.timers.clone(),
1332 history: self.history.clone(),
1333 done_emitted: self.done_emitted.clone(),
1334 deferred: self.deferred.clone(),
1335 spawn_counter: self.spawn_counter,
1336 external_source: self.external_source.clone(),
1337 status: self.status,
1338 queue: self.queue.clone(),
1339 }
1340 }
1341 fn restore_state(&mut self, s: InstanceState) {
1342 self.active = s.active;
1343 self.esvs = s.esvs;
1344 self.timers = s.timers;
1345 self.history = s.history;
1346 self.done_emitted = s.done_emitted;
1347 self.deferred = s.deferred;
1348 self.spawn_counter = s.spawn_counter;
1349 self.external_source = s.external_source;
1350 self.status = s.status;
1351 self.queue = s.queue;
1352 }
1353}
1354
1355struct InstanceState {
1356 active: BTreeSet<NodeId>,
1357 esvs: BTreeMap<String, Value>,
1358 timers: Vec<ArmedTimer>,
1359 history: BTreeMap<NodeId, HistRecord>,
1360 done_emitted: HashSet<NodeId>,
1361 deferred: Vec<QItem>,
1362 spawn_counter: u64,
1363 external_source: BTreeMap<String, Value>,
1364 status: Status,
1365 queue: VecDeque<QItem>,
1366}
1367
1368enum HKind {
1371 Transition(usize),
1372 Timer(usize),
1373}
1374struct Handler {
1375 state: NodeId,
1376 kind: HKind,
1377}
1378
1379fn find_handlers(inst: &Instance, m: &Machine, etype: &str, event_value: &Value) -> Vec<Handler> {
1380 let leaves = active_leaves(inst, m);
1381 let mut found: Vec<Handler> = Vec::new();
1382 let mut seen: HashSet<NodeId> = HashSet::new();
1383 for leaf in leaves {
1384 for s in ancestors_inclusive(m, leaf) {
1385 if !inst.active.contains(&s) {
1386 continue;
1387 }
1388 if let Some(list) = m.get(s).on_events.get(etype) {
1389 let env = build_env(inst, m, s, event_value);
1390 let mut matched = None;
1391 for (i, t) in list.iter().enumerate() {
1392 let pass = match &t.guard {
1393 Some(g) => cel::eval_bool(g, &env).unwrap_or(false),
1394 None => true,
1395 };
1396 if pass {
1397 matched = Some(i);
1398 break;
1399 }
1400 }
1401 if let Some(i) = matched {
1402 if seen.insert(s) {
1403 found.push(Handler { state: s, kind: HKind::Transition(i) });
1404 }
1405 }
1406 break; }
1408 }
1409 }
1410 found
1411}
1412
1413fn scope_has_error_handler(inst: &Instance, m: &Machine) -> bool {
1414 inst.active
1415 .iter()
1416 .any(|&n| m.get(n).on_events.contains_key("error"))
1417}
1418
1419fn execute_handler(
1422 inst: &mut Instance,
1423 m: &Machine,
1424 h: &Handler,
1425 event_value: &Value,
1426 etype: Option<&str>,
1427 buf: &mut StepBuf,
1428 rec: &mut StepRecord,
1429) -> Result<(), CelError> {
1430 match &h.kind {
1431 HKind::Timer(idx) => {
1432 let after = m.get(h.state).after[*idx].clone();
1433 if let Some(g) = &after.guard {
1434 let env = build_env(inst, m, h.state, event_value);
1435 if !cel::eval_bool(g, &env)? {
1436 return Ok(());
1437 }
1438 }
1439 run_actions(inst, m, h.state, &after.action, event_value, etype, buf, rec)?;
1440 if let Some(target) = after.target {
1441 rec.transition = Some(m.get(target).id.clone());
1442 external_transition(inst, m, h.state, target, &[], event_value, etype, buf, rec)?;
1443 }
1444 Ok(())
1445 }
1446 HKind::Transition(idx) => {
1447 let list = m.get(h.state).on_events.get(etype.unwrap_or("")).cloned();
1448 let list = match list {
1449 Some(l) => l,
1450 None => return Ok(()),
1451 };
1452 let t = list[*idx].clone();
1453 if t.target.is_none() || t.internal {
1454 run_actions(inst, m, h.state, &t.action, event_value, etype, buf, rec)?;
1455 rec.transition = None;
1456 return Ok(());
1457 }
1458 let target = t.target.unwrap();
1459 if t.local {
1460 rec.transition = Some(m.get(target).id.clone());
1461 run_actions(inst, m, h.state, &t.action, event_value, etype, buf, rec)?;
1462 local_transition(inst, m, h.state, target, event_value, etype, buf, rec)?;
1463 return Ok(());
1464 }
1465 rec.transition = Some(m.get(target).id.clone());
1466 let is_choice = m.get(target).kind == StateKind::Choice;
1467 if is_choice {
1468 run_actions(inst, m, h.state, &t.action, event_value, etype, buf, rec)?;
1469 let final_target = resolve_choice(inst, m, h.state, target, event_value, etype, buf, rec)?;
1470 external_transition(inst, m, h.state, final_target, &[], event_value, etype, buf, rec)?;
1471 } else {
1472 external_transition(inst, m, h.state, target, &t.action, event_value, etype, buf, rec)?;
1473 }
1474 Ok(())
1475 }
1476 }
1477}
1478
1479fn resolve_choice(
1480 inst: &mut Instance,
1481 m: &Machine,
1482 source: NodeId,
1483 start: NodeId,
1484 event_value: &Value,
1485 etype: Option<&str>,
1486 buf: &mut StepBuf,
1487 rec: &mut StepRecord,
1488) -> Result<NodeId, CelError> {
1489 let mut cur = start;
1490 loop {
1491 let branches = m.get(cur).choice.clone().unwrap_or_default();
1492 let env = build_env(inst, m, source, event_value);
1493 let mut chosen: Option<usize> = None;
1494 for (i, b) in branches.iter().enumerate() {
1495 let pass = match &b.guard {
1496 Some(g) => cel::eval_bool(g, &env)?,
1497 None => true,
1498 };
1499 if pass {
1500 chosen = Some(i);
1501 break;
1502 }
1503 }
1504 let i = chosen.ok_or_else(|| CelError::Other("no choice branch matched".into()))?;
1505 let b = branches[i].clone();
1506 run_actions(inst, m, source, &b.action, event_value, etype, buf, rec)?;
1507 if m.get(b.target).kind == StateKind::Choice {
1508 cur = b.target;
1509 continue;
1510 }
1511 return Ok(b.target);
1512 }
1513}
1514
1515fn external_transition(
1516 inst: &mut Instance,
1517 m: &Machine,
1518 source: NodeId,
1519 target: NodeId,
1520 action: &[Action],
1521 event_value: &Value,
1522 etype: Option<&str>,
1523 buf: &mut StepBuf,
1524 rec: &mut StepRecord,
1525) -> Result<(), CelError> {
1526 let lca = lca_external(m, source, target);
1527 let anchor = child_below(m, lca, source);
1528 let mut to_exit: Vec<NodeId> = inst
1530 .active
1531 .iter()
1532 .copied()
1533 .filter(|&x| is_strictly_below(m, lca, x) && is_ancestor_or_equal(m, anchor, x))
1534 .collect();
1535 to_exit.sort_by(|a, b| m.get(*b).depth.cmp(&m.get(*a).depth));
1536 for &x in &to_exit {
1538 if m.get(x).has_history() {
1539 record_history(inst, m, x);
1540 }
1541 }
1542 for x in to_exit {
1543 exit_state(inst, m, x, buf, rec);
1544 }
1545 if !action.is_empty() {
1546 run_actions(inst, m, source, action, event_value, etype, buf, rec)?;
1547 }
1548 let path = entry_path_below(m, lca, target);
1549 for &s in &path {
1550 enter(inst, m, s, 0, event_value, etype, buf, rec);
1551 }
1552 if let Some(&tgt) = path.last() {
1553 descend(inst, m, tgt, 0, event_value, etype, buf, rec);
1554 }
1555 Ok(())
1556}
1557
1558fn local_transition(
1559 inst: &mut Instance,
1560 m: &Machine,
1561 source: NodeId,
1562 target: NodeId,
1563 event_value: &Value,
1564 etype: Option<&str>,
1565 buf: &mut StepBuf,
1566 rec: &mut StepRecord,
1567) -> Result<(), CelError> {
1568 let mut to_exit: Vec<NodeId> = inst
1569 .active
1570 .iter()
1571 .copied()
1572 .filter(|&x| is_strictly_below(m, source, x))
1573 .collect();
1574 to_exit.sort_by(|a, b| m.get(*b).depth.cmp(&m.get(*a).depth));
1575 for &x in &to_exit {
1576 if m.get(x).has_history() {
1577 record_history(inst, m, x);
1578 }
1579 }
1580 for x in to_exit {
1581 exit_state(inst, m, x, buf, rec);
1582 }
1583 let path = entry_path_below(m, source, target);
1584 for &s in &path {
1585 enter(inst, m, s, 0, event_value, etype, buf, rec);
1586 }
1587 if let Some(&tgt) = path.last() {
1588 descend(inst, m, tgt, 0, event_value, etype, buf, rec);
1589 }
1590 Ok(())
1591}
1592
1593fn lca_external(m: &Machine, source: NodeId, target: NodeId) -> NodeId {
1595 let sa = proper_ancestors(m, source);
1596 let ta: HashSet<NodeId> = proper_ancestors(m, target).into_iter().collect();
1597 for a in sa {
1598 if ta.contains(&a) {
1599 return a;
1600 }
1601 }
1602 m.top
1603}
1604
1605fn child_below(m: &Machine, anc: NodeId, n: NodeId) -> NodeId {
1607 let mut cur = n;
1608 while let Some(p) = m.get(cur).parent {
1609 if p == anc {
1610 return cur;
1611 }
1612 cur = p;
1613 }
1614 n
1615}
1616
1617fn entry_path_below(m: &Machine, lca: NodeId, target: NodeId) -> Vec<NodeId> {
1618 let chain = ancestors_inclusive(m, target); let mut path: Vec<NodeId> = chain.into_iter().rev().collect(); while path.first().map(|&x| x != lca).unwrap_or(false) {
1621 path.remove(0);
1622 }
1623 if path.first() == Some(&lca) {
1624 path.remove(0);
1625 }
1626 path
1627}
1628
1629fn enter(
1633 inst: &mut Instance,
1634 m: &Machine,
1635 n: NodeId,
1636 clock: u64,
1637 event_value: &Value,
1638 etype: Option<&str>,
1639 buf: &mut StepBuf,
1640 rec: &mut StepRecord,
1641) {
1642 inst.active.insert(n);
1643 let sd = m.get(n);
1644 if sd.is_sm_boundary && !sd.sm_with.is_empty() {
1647 if let Some(parent) = sd.parent {
1648 let env = build_env(inst, m, parent, event_value);
1649 for (name, expr) in &sd.sm_with {
1650 if let Ok(v) = crate::cel::eval(expr, &env) {
1651 inst.external_source.insert(name.clone(), v);
1652 }
1653 }
1654 }
1655 }
1656 for (name, decl) in sd.esvs.clone() {
1657 let val = if decl.external {
1658 inst.external_source.get(&name).cloned().unwrap_or(Value::Null)
1659 } else {
1660 decl.init.clone().unwrap_or(Value::Null)
1661 };
1662 inst.esvs.insert(esv_key(sd, &name), val);
1663 }
1664 let entry = sd.entry.clone();
1665 let _ = run_actions(inst, m, n, &entry, event_value, etype, buf, rec);
1666 let afters = sd.after.clone();
1667 for (i, a) in afters.iter().enumerate() {
1668 inst.timers.push(ArmedTimer {
1669 state: n,
1670 after_idx: i,
1671 due: clock.saturating_add(a.duration_ms),
1672 });
1673 }
1674 rec.entered.push(sd.id.clone());
1675}
1676
1677fn descend(
1679 inst: &mut Instance,
1680 m: &Machine,
1681 n: NodeId,
1682 clock: u64,
1683 event_value: &Value,
1684 etype: Option<&str>,
1685 buf: &mut StepBuf,
1686 rec: &mut StepRecord,
1687) {
1688 let sd = m.get(n);
1689 match sd.kind {
1690 StateKind::Composite => {
1691 let has_hist = sd.has_history();
1692 let hist = inst.history.get(&n).cloned();
1693 if has_hist && hist.is_some() {
1694 restore_history(inst, m, n, clock, event_value, etype, hist.unwrap(), buf, rec);
1695 } else if let Some(init) = sd.initial.clone() {
1696 for a in &init.action {
1697 let _ = run_actions(inst, m, n, std::slice::from_ref(a), event_value, etype, buf, rec);
1698 }
1699 enter(inst, m, init.target, clock, event_value, etype, buf, rec);
1700 descend(inst, m, init.target, clock, event_value, etype, buf, rec);
1701 }
1702 }
1703 StateKind::Orthogonal => {
1704 let has_hist = sd.has_history();
1705 let hist = inst.history.get(&n).cloned();
1706 if has_hist && hist.is_some() {
1707 restore_history(inst, m, n, clock, event_value, etype, hist.unwrap(), buf, rec);
1708 } else {
1709 let regions = sd.regions.clone();
1710 for r in ®ions {
1711 for a in &r.initial.action {
1712 let _ = run_actions(inst, m, n, std::slice::from_ref(a), event_value, etype, buf, rec);
1713 }
1714 enter(inst, m, r.initial.target, clock, event_value, etype, buf, rec);
1715 descend(inst, m, r.initial.target, clock, event_value, etype, buf, rec);
1716 }
1717 }
1718 }
1719 _ => {}
1720 }
1721}
1722
1723fn restore_history(
1724 inst: &mut Instance,
1725 m: &Machine,
1726 state: NodeId,
1727 clock: u64,
1728 event_value: &Value,
1729 etype: Option<&str>,
1730 hist: HistRecord,
1731 buf: &mut StepBuf,
1732 rec: &mut StepRecord,
1733) {
1734 match hist {
1735 HistRecord::Shallow(direct) => {
1736 for child in direct {
1737 enter(inst, m, child, clock, event_value, etype, buf, rec);
1738 descend(inst, m, child, clock, event_value, etype, buf, rec);
1739 }
1740 }
1741 HistRecord::Deep(leaves) => {
1742 for leaf in leaves {
1743 let path = entry_path_below(m, state, leaf);
1744 for &s in &path {
1745 enter(inst, m, s, clock, event_value, etype, buf, rec);
1746 }
1747 }
1748 }
1749 }
1750}
1751
1752fn exit_state(
1753 inst: &mut Instance,
1754 m: &Machine,
1755 n: NodeId,
1756 buf: &mut StepBuf,
1757 rec: &mut StepRecord,
1758) {
1759 let sd = m.get(n);
1760 inst.done_emitted.remove(&n);
1761 let exit = sd.exit.clone();
1762 let _ = run_actions(inst, m, n, &exit, &Value::Null, None, buf, rec);
1763 for (name, _) in sd.esvs.clone() {
1764 inst.esvs.remove(&esv_key(sd, &name));
1765 }
1766 inst.timers.retain(|t| t.state != n);
1767 inst.active.remove(&n);
1768 rec.exited.push(sd.id.clone());
1769}
1770
1771fn exit_up_to(
1772 inst: &mut Instance,
1773 m: &Machine,
1774 leaf: NodeId,
1775 lca: Option<NodeId>,
1776 buf: &mut StepBuf,
1777 rec: &mut StepRecord,
1778) {
1779 let mut cur = Some(leaf);
1780 while let Some(c) = cur {
1781 if lca == Some(c) {
1782 break;
1783 }
1784 exit_state(inst, m, c, buf, rec);
1785 cur = m.get(c).parent;
1786 }
1787}
1788
1789fn record_history(inst: &mut Instance, m: &Machine, state: NodeId) {
1790 let sd = m.get(state);
1791 match sd.history {
1792 HistoryKind::Shallow => {
1793 let direct: Vec<NodeId> = sd
1794 .children
1795 .iter()
1796 .copied()
1797 .filter(|&c| inst.active.contains(&c))
1798 .collect();
1799 inst.history.insert(state, HistRecord::Shallow(direct));
1800 }
1801 HistoryKind::Deep => {
1802 let leaves: Vec<NodeId> = inst
1803 .active
1804 .iter()
1805 .copied()
1806 .filter(|&n| is_leaf(m, n) && is_strictly_below(m, state, n))
1807 .collect();
1808 inst.history.insert(state, HistRecord::Deep(leaves));
1809 }
1810 HistoryKind::None => {}
1811 }
1812}
1813
1814fn run_actions(
1817 inst: &mut Instance,
1818 m: &Machine,
1819 scope: NodeId,
1820 actions: &[Action],
1821 event_value: &Value,
1822 etype: Option<&str>,
1823 buf: &mut StepBuf,
1824 rec: &mut StepRecord,
1825) -> Result<(), CelError> {
1826 for a in actions {
1827 run_one(inst, m, scope, a, event_value, etype, buf, rec)?;
1828 }
1829 Ok(())
1830}
1831
1832fn run_one(
1833 inst: &mut Instance,
1834 m: &Machine,
1835 scope: NodeId,
1836 action: &Action,
1837 event_value: &Value,
1838 etype: Option<&str>,
1839 buf: &mut StepBuf,
1840 _rec: &mut StepRecord,
1841) -> Result<(), CelError> {
1842 let env = build_env(inst, m, scope, event_value);
1843 match action {
1844 Action::Assign(pairs) => {
1845 for (name, expr) in pairs {
1846 let val = cel::eval(expr, &env)?;
1847 let decl_state = nearest_declaring(inst, m, scope, name)
1848 .ok_or_else(|| CelError::Other(format!("assign to undeclared '{name}'")))?;
1849 let sd = m.get(decl_state);
1850 let decl = sd
1851 .esvs
1852 .iter()
1853 .find(|(n, _)| n == name)
1854 .map(|(_, d)| d.clone())
1855 .unwrap();
1856 if decl.external {
1857 return Err(CelError::Other(format!("cannot assign to external esv '{name}'")));
1858 }
1859 let coerced = val
1860 .coerce_to(&decl.ty)
1861 .ok_or_else(|| CelError::Type(format!("'{name}' expects {}", decl.ty)))?;
1862 inst.esvs.insert(esv_key(sd, name), coerced);
1863 }
1864 Ok(())
1865 }
1866 Action::Publish { event, to, payload } => {
1867 let pmap = eval_payload(payload, &env)?;
1868 buf.published.push(event.clone());
1869 if let Some(to_expr) = to {
1870 for t in eval_targets(to_expr, &env)? {
1871 buf.deliveries.push((
1872 t,
1873 QueuedEvent {
1874 etype: event.clone(),
1875 payload: Value::Map(pmap.clone()),
1876 origin: Some(inst.id.clone()),
1877 },
1878 ));
1879 }
1880 } else {
1881 buf.undirected.push((inst.id.clone(), event.clone(), Value::Map(pmap)));
1882 }
1883 Ok(())
1884 }
1885 Action::Refresh { only } => {
1886 if etype != Some("env") {
1887 return Err(CelError::Other("refresh only valid while handling env".into()));
1888 }
1889 if let Some(changed) = changed_of(event_value) {
1890 let names: Vec<String> = match only {
1891 Some(lst) => lst.clone(),
1892 None => changed.keys().cloned().collect(),
1893 };
1894 for name in names {
1895 if let Some(v) = changed.get(&name) {
1896 if let Some(st) = nearest_declaring(inst, m, scope, &name) {
1897 inst.esvs.insert(esv_key(m.get(st), &name), v.clone());
1898 }
1899 }
1900 }
1901 }
1902 Ok(())
1903 }
1904 Action::Spawn { def, payload, result } => {
1905 let pmap = eval_payload(payload, &env)?;
1906 buf.spawns.push(SpawnReq {
1907 parent: inst.id.clone(),
1908 scope,
1909 def_id: def.clone(),
1910 payload: pmap,
1911 result_var: result.clone(),
1912 });
1913 buf.spawned.push(def.clone());
1914 buf.spawned_def.push(def.clone());
1915 Ok(())
1916 }
1917 Action::Stop => {
1918 buf.stop = true;
1919 inst.queue.push_back(QItem::Event(QueuedEvent {
1921 etype: "__stop__".into(),
1922 payload: Value::Null,
1923 origin: Some(inst.id.clone()),
1924 }));
1925 Ok(())
1926 }
1927 }
1928}
1929
1930fn eval_payload(pairs: &[(String, String)], env: &Env) -> Result<BTreeMap<String, Value>, CelError> {
1931 let mut out = BTreeMap::new();
1932 for (k, expr) in pairs {
1933 out.insert(k.clone(), cel::eval(expr, env)?);
1934 }
1935 Ok(out)
1936}
1937
1938fn eval_targets(expr: &str, env: &Env) -> Result<Vec<InstanceId>, CelError> {
1939 match cel::eval(expr, env)? {
1940 Value::Str(s) => Ok(vec![s]),
1941 Value::List(l) => Ok(l
1942 .into_iter()
1943 .filter_map(|x| x.as_str_value().map(|s| s.to_string()))
1944 .collect()),
1945 Value::Null => Ok(vec![]),
1946 other => Err(CelError::Type(format!("publish.to must be string/list, got {}", other.type_name()))),
1947 }
1948}
1949
1950fn effective_defer(inst: &Instance, m: &Machine) -> HashSet<String> {
1953 let mut out = HashSet::new();
1954 for &n in &inst.active {
1955 for d in &m.get(n).defer {
1956 out.insert(d.clone());
1957 }
1958 }
1959 out
1960}
1961
1962fn replay_deferred(inst: &mut Instance, m: &Machine) {
1963 let defer_set = effective_defer(inst, m);
1964 let (mut move_front, mut remaining) = (Vec::new(), Vec::new());
1965 for item in inst.deferred.drain(..) {
1966 let still = match &item {
1967 QItem::Event(e) => defer_set.contains(&e.etype),
1968 QItem::Timer { .. } => false,
1969 };
1970 if still {
1971 remaining.push(item);
1972 } else {
1973 move_front.push(item);
1974 }
1975 }
1976 inst.deferred = remaining;
1977 let mut new_q = VecDeque::new();
1978 for item in move_front {
1979 new_q.push_back(item);
1980 }
1981 while let Some(x) = inst.queue.pop_front() {
1982 new_q.push_back(x);
1983 }
1984 inst.queue = new_q;
1985}
1986
1987fn complete_states(inst: &Instance, m: &Machine) -> Vec<NodeId> {
1993 let leaves = active_leaves(inst, m);
1994 let mut out = Vec::new();
1995 for &s in &inst.active {
1996 let sd = m.get(s);
1997 match sd.kind {
1998 StateKind::Orthogonal => {
1999 if regions_all_final(inst, m, s) {
2000 out.push(s);
2001 }
2002 }
2003 StateKind::Composite => {
2004 if leaves.iter().any(|&l| {
2005 is_ancestor_or_equal(m, s, l) && m.get(l).kind == StateKind::Final
2006 }) {
2007 out.push(s);
2008 }
2009 }
2010 _ => {}
2011 }
2012 }
2013 out
2014}
2015
2016fn composite_leaf_name(inst: &Instance, m: &Machine, composite: NodeId) -> String {
2018 for l in active_leaves(inst, m) {
2019 if is_ancestor_or_equal(m, composite, l) && m.get(l).kind == StateKind::Final {
2020 return m.get(l).id.clone();
2021 }
2022 }
2023 String::new()
2024}
2025
2026fn regions_all_final(inst: &Instance, m: &Machine, ortho: NodeId) -> bool {
2027 let regions = m.get(ortho).regions.clone();
2028 if regions.is_empty() {
2029 return false;
2030 }
2031 for r in ®ions {
2032 let any_final = r.states.iter().any(|&s| subtree_has_active_final(inst, m, s));
2033 if !any_final {
2034 return false;
2035 }
2036 }
2037 true
2038}
2039
2040fn subtree_has_active_final(inst: &Instance, m: &Machine, n: NodeId) -> bool {
2041 if inst.active.contains(&n) && m.get(n).kind == StateKind::Final {
2042 return true;
2043 }
2044 for &c in &m.get(n).children {
2045 if subtree_has_active_final(inst, m, c) {
2046 return true;
2047 }
2048 }
2049 false
2050}
2051
2052fn build_env(inst: &Instance, m: &Machine, scope: NodeId, event_value: &Value) -> Env {
2055 let mut env = Env::new();
2056 for (k, v) in resolve_visible(inst, m, scope) {
2057 env = env.with(k, v);
2058 }
2059 env = env.with("id", Value::Str(inst.id.clone()));
2060 env = env.with("parent", inst.parent.clone().map(Value::Str).unwrap_or(Value::Null));
2061 env = env.with("event", event_value.clone());
2062 env
2063}
2064
2065fn event_binding(e: &QueuedEvent) -> Value {
2066 let mut m = BTreeMap::new();
2067 m.insert("type".to_string(), Value::Str(e.etype.clone()));
2068 m.insert("payload".to_string(), e.payload.clone());
2069 Value::Map(m)
2070}
2071
2072fn changed_of(event_value: &Value) -> Option<BTreeMap<String, Value>> {
2074 let payload = field(event_value, "payload")?;
2075 let changed = field(&payload, "changed")?;
2076 if let Value::Map(m) = changed {
2077 Some(m)
2078 } else {
2079 None
2080 }
2081}
2082
2083fn field(v: &Value, name: &str) -> Option<Value> {
2084 if let Value::Map(m) = v {
2085 m.get(name).cloned()
2086 } else {
2087 None
2088 }
2089}
2090
2091fn error_payload(fault: &CelError) -> BTreeMap<String, Value> {
2092 let mut m = BTreeMap::new();
2093 m.insert("message".to_string(), Value::Str(fault.to_string()));
2094 m
2095}
2096
2097fn validate_event_payload(m: &Machine, etype: &str, payload: &Value) -> Result<(), String> {
2100 if matches!(etype, "env" | "error" | "done" | "initial" | "entry" | "exit") {
2102 return Ok(());
2103 }
2104 let decl = match m.events.get(etype) {
2105 Some(d) => d,
2106 None => return Err(format!("undeclared event '{etype}'")),
2107 };
2108 let pm = match payload {
2109 Value::Map(m) => m.clone(),
2110 Value::Null => BTreeMap::new(),
2111 _ => return Err("payload must be a map".to_string()),
2112 };
2113 for (name, field) in &decl.payload {
2114 match pm.get(name) {
2115 None => {
2116 if field.required {
2117 return Err(format!("missing required field '{name}'"));
2118 }
2119 }
2120 Some(v) => {
2121 if !v.matches_type(&field.ty) {
2122 return Err(format!("field '{name}' expects {}", field.ty));
2123 }
2124 }
2125 }
2126 }
2127 for name in pm.keys() {
2128 if !decl.payload.iter().any(|(n, _)| n == name) {
2129 return Err(format!("unexpected field '{name}'"));
2130 }
2131 }
2132 Ok(())
2133}
2134
2135fn config_of(inst: &Instance, m: &Machine) -> Vec<String> {
2138 let mut v: Vec<String> = active_leaves(inst, m).into_iter().map(|n| m.get(n).id.clone()).collect();
2139 v.sort();
2140 v
2141}
2142
2143fn reported_esvs(inst: &Instance, m: &Machine) -> BTreeMap<String, Value> {
2144 let mut leaves = active_leaves(inst, m);
2145 leaves.sort_by_key(|&n| std::cmp::Reverse(m.get(n).depth));
2146 let mut out = BTreeMap::new();
2147 for leaf in leaves {
2148 for (k, v) in resolve_visible(inst, m, leaf) {
2149 out.entry(k).or_insert(v);
2150 }
2151 }
2152 out
2153}
2154
2155const RESERVED_LIFECYCLE_EVENTS: &[&str] = &["initial", "entry", "exit", "env", "error", "done"];
2157
2158fn enabled_events(inst: &Instance, m: &Machine) -> Vec<String> {
2164 let mut set = BTreeSet::new();
2165 for leaf in active_leaves(inst, m) {
2166 for s in ancestors_inclusive(m, leaf) {
2167 for ev in m.get(s).on_events.keys() {
2168 if !RESERVED_LIFECYCLE_EVENTS.contains(&ev.as_str()) {
2169 set.insert(ev.clone());
2170 }
2171 }
2172 }
2173 }
2174 set.into_iter().collect()
2175}
2176
2177