Skip to main content

telltale_vm/
nested.rs

1//! Nested VM handler for distributed simulation.
2//!
3//! The outer VM schedules site coroutines; each site handler advances an
4//! inner VM that runs site-local protocols.
5
6use std::collections::BTreeMap;
7use std::sync::Mutex;
8
9use crate::coroutine::Value;
10use crate::effect::{EffectFailure, EffectHandler, EffectResult};
11use crate::vm::{ObsEvent, StepResult, VMError, VM};
12
13struct SiteRunner {
14    vm: Mutex<VM>,
15    handler: Box<dyn EffectHandler>,
16}
17
18/// Effect handler that dispatches to inner VMs keyed by outer role name.
19pub struct NestedVMHandler {
20    sites: BTreeMap<String, SiteRunner>,
21    max_rounds_per_step: usize,
22}
23
24impl NestedVMHandler {
25    /// Create an empty nested handler.
26    #[must_use]
27    pub fn new() -> Self {
28        Self {
29            sites: BTreeMap::new(),
30            max_rounds_per_step: 1,
31        }
32    }
33
34    /// Set how many inner VM rounds to advance per outer handler call.
35    #[must_use]
36    pub fn with_rounds_per_step(mut self, rounds: usize) -> Self {
37        self.max_rounds_per_step = rounds.max(1);
38        self
39    }
40
41    /// Number of inner VM rounds attempted per outer handler call.
42    #[must_use]
43    pub fn rounds_per_step(&self) -> usize {
44        self.max_rounds_per_step
45    }
46
47    /// Register a site by name with its inner VM and handler.
48    pub fn add_site(&mut self, name: impl Into<String>, vm: VM, handler: Box<dyn EffectHandler>) {
49        self.sites.insert(
50            name.into(),
51            SiteRunner {
52                vm: Mutex::new(vm),
53                handler,
54            },
55        );
56    }
57
58    /// Get a copy of the inner VM trace for a site.
59    ///
60    /// # Panics
61    ///
62    /// Panics if the site VM mutex is poisoned.
63    #[must_use]
64    pub fn site_trace(&self, name: &str) -> Option<Vec<ObsEvent>> {
65        self.sites.get(name).map(|site| {
66            site.vm
67                .lock()
68                .unwrap_or_else(|poisoned| poisoned.into_inner())
69                .trace()
70                .to_vec()
71        })
72    }
73
74    /// Check whether all coroutines in a site VM are terminal.
75    ///
76    /// # Panics
77    ///
78    /// Panics if the site VM mutex is poisoned.
79    #[must_use]
80    pub fn site_all_done(&self, name: &str) -> Option<bool> {
81        self.sites.get(name).map(|site| {
82            site.vm
83                .lock()
84                .unwrap_or_else(|poisoned| poisoned.into_inner())
85                .all_done()
86        })
87    }
88
89    fn step_site(&self, name: &str) -> Result<(), String> {
90        let site = self
91            .sites
92            .get(name)
93            .ok_or_else(|| format!("unknown site: {name}"))?;
94
95        let mut vm = site
96            .vm
97            .lock()
98            .unwrap_or_else(|poisoned| poisoned.into_inner());
99        let handler = site.handler.as_ref();
100
101        for _ in 0..self.max_rounds_per_step {
102            match vm.step_round(handler, 1) {
103                Ok(StepResult::Continue) => {}
104                Ok(StepResult::AllDone | StepResult::Stuck) => break,
105                Err(VMError::Fault { fault, .. }) => {
106                    return Err(format!("inner vm fault: {fault}"));
107                }
108                Err(e) => return Err(e.to_string()),
109            }
110        }
111
112        Ok(())
113    }
114}
115
116impl Default for NestedVMHandler {
117    fn default() -> Self {
118        Self::new()
119    }
120}
121
122impl EffectHandler for NestedVMHandler {
123    fn handle_send(
124        &self,
125        role: &str,
126        _partner: &str,
127        _label: &str,
128        _state: &[Value],
129    ) -> EffectResult<Value> {
130        match self.step_site(role) {
131            Ok(()) => EffectResult::success(Value::Unit),
132            Err(message) => EffectResult::failure(EffectFailure::contract_violation(message)),
133        }
134    }
135
136    fn handle_recv(
137        &self,
138        role: &str,
139        _partner: &str,
140        _label: &str,
141        _state: &mut Vec<Value>,
142        _payload: &Value,
143    ) -> EffectResult<()> {
144        match self.step_site(role) {
145            Ok(()) => EffectResult::success(()),
146            Err(message) => EffectResult::failure(EffectFailure::contract_violation(message)),
147        }
148    }
149
150    fn handle_choose(
151        &self,
152        _role: &str,
153        _partner: &str,
154        labels: &[String],
155        _state: &[Value],
156    ) -> EffectResult<String> {
157        match labels.first().cloned() {
158            Some(label) => EffectResult::success(label),
159            None => EffectResult::failure(EffectFailure::invalid_input("no labels available")),
160        }
161    }
162
163    fn step(&self, role: &str, _state: &mut Vec<Value>) -> EffectResult<()> {
164        match self.step_site(role) {
165            Ok(()) => EffectResult::success(()),
166            Err(message) => EffectResult::failure(EffectFailure::contract_violation(message)),
167        }
168    }
169}