Skip to main content

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}