1use std::any::Any;
12
13use indexmap::{IndexMap, IndexSet};
14use serde::{Deserialize, Serialize};
15
16use super::{
17 BspContext,
18 error::BspError,
19 graph::{EdgeId, InputPin, NodeId, OutputPin, PortIndex},
20 value::{BspOutputMapExt, BspValue},
21};
22
23#[derive(Debug, Default)]
28pub struct ValueStore {
29 pub(crate) outputs: IndexMap<OutputPin, BspValue>,
30 pub(crate) injected: IndexMap<InputPin, BspValue>,
31}
32
33impl ValueStore {
34 #[must_use]
36 pub fn get_output(&self, pin: OutputPin) -> Option<&BspValue> {
37 self.outputs.get(&pin)
38 }
39
40 #[must_use]
42 pub fn get_output_typed<T: Any + Send + Sync + Clone>(&self, pin: OutputPin) -> Option<T> {
43 self.outputs.get(&pin)?.downcast_ref::<T>().cloned()
44 }
45
46 #[must_use]
48 pub const fn outputs(&self) -> &IndexMap<OutputPin, BspValue> {
49 &self.outputs
50 }
51
52 #[must_use]
54 pub fn get_injected(&self, pin: InputPin) -> Option<&BspValue> {
55 self.injected.get(&pin)
56 }
57
58 #[must_use]
60 pub const fn injected(&self) -> &IndexMap<InputPin, BspValue> {
61 &self.injected
62 }
63
64 pub fn set_output_typed<T: Any + Send + Sync + 'static>(&mut self, pin: OutputPin, value: T) {
68 self.outputs.insert(pin, BspValue::new(value));
69 }
70
71 pub fn set_injected_typed<T: Any + Send + Sync + 'static>(&mut self, pin: InputPin, value: T) {
73 self.injected.insert(pin, BspValue::new(value));
74 }
75}
76
77#[derive(Debug)]
80pub struct RunState<S> {
81 pub user: S,
83 pub(crate) values: ValueStore,
84 pub(crate) pending_tokens: IndexMap<EdgeId, u64>,
86 pub(crate) iterations: IndexMap<EdgeId, u64>,
88 pub(crate) executions: IndexMap<NodeId, u64>,
90 pub errors: IndexMap<NodeId, BspError>,
92 pub(crate) frontier: IndexSet<NodeId>,
94 pub step: u64,
96}
97
98impl<S> RunState<S> {
99 pub(crate) fn new(user: S) -> Self {
101 Self {
102 user,
103 values: ValueStore::default(),
104 pending_tokens: IndexMap::new(),
105 iterations: IndexMap::new(),
106 executions: IndexMap::new(),
107 errors: IndexMap::new(),
108 frontier: IndexSet::new(),
109 step: 0,
110 }
111 }
112
113 #[must_use]
115 pub const fn values(&self) -> &ValueStore {
116 &self.values
117 }
118
119 pub const fn values_mut(&mut self) -> &mut ValueStore {
121 &mut self.values
122 }
123
124 #[must_use]
126 pub const fn iterations(&self) -> &IndexMap<EdgeId, u64> {
127 &self.iterations
128 }
129
130 #[must_use]
132 pub const fn executions(&self) -> &IndexMap<NodeId, u64> {
133 &self.executions
134 }
135
136 #[must_use]
138 pub fn execution_count(&self, node_id: NodeId) -> u64 {
139 self.executions.get(&node_id).copied().unwrap_or(0)
140 }
141
142 #[must_use]
144 pub fn iteration_count(&self, edge: EdgeId) -> u64 {
145 self.iterations.get(&edge).copied().unwrap_or(0)
146 }
147
148 #[must_use]
150 pub fn get_output_typed<T: Any + Send + Sync + Clone>(&self, pin: OutputPin) -> Option<T> {
151 self.values.get_output_typed(pin)
152 }
153}
154
155pub struct RunContext<'a, S> {
168 pub state: &'a mut S,
171 pub node_id: NodeId,
173 pub ctx: &'a BspContext,
175 pub execution_no: u64,
177 pub(crate) inputs: IndexMap<PortIndex, BspValue>,
178 pub(crate) outputs: IndexMap<PortIndex, BspValue>,
179 pub(crate) skip: bool,
180}
181
182impl<'a, S> RunContext<'a, S> {
183 pub(crate) fn new(
184 state: &'a mut S,
185 node_id: NodeId,
186 ctx: &'a BspContext,
187 execution_no: u64,
188 inputs: IndexMap<PortIndex, BspValue>,
189 ) -> Self {
190 Self {
191 state,
192 node_id,
193 ctx,
194 execution_no,
195 inputs,
196 outputs: IndexMap::new(),
197 skip: false,
198 }
199 }
200
201 #[must_use]
203 pub fn input<T: Any + Send + Sync + Clone>(&self, port: PortIndex) -> Option<T> {
204 self.inputs.get(&port)?.downcast_ref::<T>().cloned()
205 }
206
207 #[must_use]
209 pub fn input_value(&self, port: PortIndex) -> Option<&BspValue> {
210 self.inputs.get(&port)
211 }
212
213 pub fn take_input(&mut self, port: PortIndex) -> Option<BspValue> {
216 self.inputs.swap_remove(&port)
217 }
218
219 pub fn set_output<T: Any + Send + Sync>(&mut self, port: PortIndex, value: T) {
224 self.outputs.insert(port, BspValue::new(value));
225 }
226
227 pub fn skip_outputs(&mut self) {
230 self.skip = true;
231 }
232
233 pub(crate) fn take_staged(&mut self) -> (IndexMap<PortIndex, BspValue>, bool) {
235 (std::mem::take(&mut self.outputs), self.skip)
236 }
237}
238
239#[derive(Debug)]
247pub enum EngineResult<S = ()> {
248 Complete(Box<RunState<S>>),
250 Suspended(Box<SuspendedExecution<S>>),
252 StepLimitReached(Box<RunState<S>>),
254}
255
256#[derive(Debug)]
258pub struct SuspendedExecution<S = ()> {
259 pub suspensions: Vec<PendingSuspension>,
261 pub(crate) state: RunState<S>,
262}
263
264#[derive(Debug, Clone, Serialize, Deserialize)]
266pub struct PendingSuspension {
267 pub node_id: NodeId,
269 pub waiting_inputs: Vec<PortIndex>,
271 pub payload: serde_json::Value,
273}
274
275impl<S> SuspendedExecution<S> {
276 #[must_use]
278 pub const fn state(&self) -> &RunState<S> {
279 &self.state
280 }
281
282 pub const fn state_mut(&mut self) -> &mut RunState<S> {
284 &mut self.state
285 }
286
287 #[must_use]
289 pub fn into_state(self) -> RunState<S> {
290 self.state
291 }
292}
293
294#[derive(Serialize, Deserialize)]
298struct SuspensionSnapshot<S> {
299 suspensions: Vec<PendingSuspension>,
300 user: S,
301 pending_tokens: IndexMap<EdgeId, u64>,
302 iterations: IndexMap<EdgeId, u64>,
303 executions: IndexMap<NodeId, u64>,
304 frontier: Vec<NodeId>,
305 step: u64,
306}
307
308impl<S> SuspendedExecution<S>
309where
310 S: Serialize + for<'de> Deserialize<'de>,
311{
312 pub fn to_json(&self) -> Result<String, BspError> {
324 let snapshot = SuspensionSnapshot {
325 suspensions: self.suspensions.clone(),
326 user: &self.state.user,
327 pending_tokens: self.state.pending_tokens.clone(),
328 iterations: self.state.iterations.clone(),
329 executions: self.state.executions.clone(),
330 frontier: self.state.frontier.iter().copied().collect(),
331 step: self.state.step,
332 };
333 serde_json::to_string(&snapshot).map_err(|e| BspError::SuspensionSerde(e.to_string()))
334 }
335
336 pub fn from_json(json: &str) -> Result<Self, BspError> {
341 let snapshot: SuspensionSnapshot<S> =
342 serde_json::from_str(json).map_err(|e| BspError::SuspensionSerde(e.to_string()))?;
343 Ok(Self {
344 suspensions: snapshot.suspensions,
345 state: RunState {
346 user: snapshot.user,
347 values: ValueStore::default(),
348 pending_tokens: snapshot.pending_tokens,
349 iterations: snapshot.iterations,
350 executions: snapshot.executions,
351 errors: IndexMap::new(),
352 frontier: snapshot.frontier.into_iter().collect(),
353 step: snapshot.step,
354 },
355 })
356 }
357}
358
359impl<S> BspOutputMapExt for RunState<S> {
360 fn get_typed<T: Any + Send + Sync + Clone>(&self, pin: OutputPin) -> Result<T, BspError> {
363 self.values.outputs().get_typed(pin)
364 }
365}
366
367#[cfg(test)]
368mod tests {
369 use super::*;
370
371 #[test]
372 fn test_value_store_roundtrip() {
373 let mut store = ValueStore::default();
374 let pin = OutputPin {
375 node_id: 1,
376 port: 0,
377 };
378 store.set_output_typed(pin, 42_i64);
379 assert_eq!(store.get_output_typed::<i64>(pin), Some(42));
380 assert_eq!(store.get_output_typed::<String>(pin), None);
381
382 let input = InputPin {
383 node_id: 2,
384 port: 1,
385 };
386 store.set_injected_typed(input, true);
387 assert!(store.get_injected(input).is_some());
388 }
389
390 #[test]
391 fn test_run_state_counters() {
392 let mut state = RunState::new(());
393 state.executions.insert(3, 7);
394 state.iterations.insert(9, 2);
395 assert_eq!(state.execution_count(3), 7);
396 assert_eq!(state.execution_count(4), 0);
397 assert_eq!(state.iteration_count(9), 2);
398 assert_eq!(state.iteration_count(10), 0);
399 }
400
401 #[test]
402 fn test_run_context_io() {
403 let mut user = 0_u64;
404 let ctx = BspContext::new();
405 let mut inputs = IndexMap::new();
406 inputs.insert(0, BspValue::new(21_i64));
407 {
408 let mut run = RunContext::new(&mut user, 5, &ctx, 3, inputs);
409 assert_eq!(run.node_id, 5);
410 assert_eq!(run.execution_no, 3);
411 assert_eq!(run.input::<i64>(0), Some(21));
412 assert_eq!(
413 run.take_input(0)
414 .and_then(|v| v.downcast::<i64>().ok())
415 .map(|a| *a),
416 Some(21)
417 );
418 assert!(run.input_value(0).is_none());
419 run.set_output(1, "done".to_string());
420 run.skip_outputs();
421 let (staged, skip) = run.take_staged();
422 assert!(skip);
423 assert!(staged.contains_key(&1));
424 }
425 }
426
427 #[test]
428 fn test_suspension_snapshot_roundtrip_preserves_counters() {
429 let mut state: RunState<String> = RunState::new("consumer-state".to_string());
430 state.pending_tokens.insert(4, 2);
431 state.iterations.insert(4, 5);
432 state.executions.insert(1, 6);
433 state.frontier.insert(1);
434 state.step = 11;
435 let suspended = SuspendedExecution {
436 suspensions: vec![PendingSuspension {
437 node_id: 1,
438 waiting_inputs: vec![0],
439 payload: serde_json::json!({"why": "test"}),
440 }],
441 state,
442 };
443
444 let json = suspended.to_json().unwrap();
445 assert!(!json.contains("outputs"));
447
448 let restored = SuspendedExecution::<String>::from_json(&json).unwrap();
449 assert_eq!(restored.state.user, "consumer-state");
450 assert_eq!(restored.state.iteration_count(4), 5);
451 assert_eq!(restored.state.execution_count(1), 6);
452 assert_eq!(restored.state.pending_tokens.get(&4), Some(&2));
453 assert_eq!(restored.state.step, 11);
454 assert!(restored.state.frontier.contains(&1));
455 assert_eq!(restored.suspensions[0].waiting_inputs, vec![0]);
456 }
457
458 #[test]
459 fn test_suspension_snapshot_rejects_garbage() {
460 let err = SuspendedExecution::<String>::from_json("{not json").unwrap_err();
461 assert!(matches!(err, BspError::SuspensionSerde(_)));
462 }
463}