dataflow_rs/observe/replay.rs
1//! On-demand trace replay, retaining one working context rather than every step.
2
3use super::Trace;
4use super::trace::{apply, valid_mapping_ends};
5use crate::base::Context;
6
7/// A trace replayed on demand, with one working context.
8///
9/// Forward seeks apply only the intervening steps. A backward seek starts
10/// again from the initial context. Memory use does not grow with the number
11/// of steps visited. Like [`Trace::contexts`], this trusts its trace; call
12/// [`Trace::validate`] first when reading an externally supplied trace.
13#[derive(Debug)]
14pub struct Replay {
15 trace: Trace,
16 context: Context,
17 /// The next step to apply: zero means the initial context.
18 next: usize,
19}
20
21impl Replay {
22 /// Takes ownership of a trace and starts at its initial context.
23 pub fn new(trace: Trace) -> Self {
24 Self {
25 context: trace.initial.clone(),
26 trace,
27 next: 0,
28 }
29 }
30
31 /// The trace being replayed.
32 pub fn trace(&self) -> &Trace {
33 &self.trace
34 }
35
36 /// The context after `step`, or the initial context for `None`.
37 /// Returns `None` for a step outside the trace, leaving the cursor unchanged.
38 pub fn context_at(&mut self, step: Option<usize>) -> Option<&Context> {
39 let end = match step {
40 Some(step) => step.checked_add(1)?,
41 None => 0,
42 };
43 if end > self.trace.steps.len() {
44 return None;
45 }
46 if end < self.next {
47 self.context = self.trace.initial.clone();
48 self.next = 0;
49 }
50 for step in &self.trace.steps[self.next..end] {
51 for change in &step.changes {
52 apply(&mut self.context.root, change);
53 }
54 }
55 self.next = end;
56 Some(&self.context)
57 }
58
59 /// The context after one mapping of `step`, before its trailing engine
60 /// writes. Returns `None` for a missing step or mapping, or malformed
61 /// mapping boundaries. Only the requested mapping's context is copied.
62 pub fn mapping_context_at(&mut self, step: usize, mapping: usize) -> Option<Context> {
63 let at = self.trace.steps.get(step)?;
64 if !valid_mapping_ends(at) {
65 return None;
66 }
67 let end = *at.mapping_ends.get(mapping)? as usize;
68 let mut context = self.context_at(step.checked_sub(1))?.clone();
69 for change in &self.trace.steps[step].changes[..end] {
70 apply(&mut context.root, change);
71 }
72 Some(context)
73 }
74}