Skip to main content

bubbles/runtime/
snapshot.rs

1//! [`RunnerSnapshot`] - session state for bookmarks and save / load support.
2
3use std::collections::{HashMap, HashSet};
4
5/// A point-in-time snapshot of the dialogue session's mutable state.
6///
7/// Use [`Runner::snapshot`](crate::runtime::Runner::snapshot) to capture and
8/// [`Runner::restore`](crate::runtime::Runner::restore) to apply.
9///
10/// The snapshot records:
11/// - which node was active (`current_node`),
12/// - how many times each node has been visited (`visits`),
13/// - which `<<once>>` blocks have already fired (`once_seen`).
14///
15/// **Variable storage is not included** - it is the host's responsibility to
16/// persist [`HashMapStorage`](crate::HashMapStorage) (or their own
17/// [`VariableStorage`](crate::VariableStorage) impl) alongside the snapshot when
18/// building a full save file.
19///
20/// With the `serde` feature, this type also derives `Serialize` / `Deserialize`
21/// so you can write it to disk as JSON or another format.
22///
23/// # Save / load example
24///
25/// Both the snapshot **and** variable storage must be serialised together when
26/// you persist to disk. Neither is complete without the other.
27///
28/// ```rust
29/// # #[cfg(feature = "serde")]
30/// # {
31/// use bubbles::{HashMapStorage, Runner, Value, VariableStorage, compile};
32///
33/// let src = "title: A\n---\n<<set $gold = 10>>\nHello!\n===\n";
34/// let prog = compile(src).unwrap();
35///
36/// // First session
37/// let mut runner = Runner::new(prog.clone(), HashMapStorage::new());
38/// runner.start("A").unwrap();
39/// let _ = runner.next_event(); // NodeStarted
40/// let _ = runner.next_event(); // (set $gold side-effect, then Line)
41///
42/// let snap = runner.snapshot();
43/// let snap_json = serde_json::to_string(&snap).unwrap();
44/// let vars_json = serde_json::to_string(runner.storage()).unwrap();
45///
46/// let saved_vars: HashMapStorage = serde_json::from_str(&vars_json).unwrap();
47/// let saved_snap: bubbles::RunnerSnapshot = serde_json::from_str(&snap_json).unwrap();
48///
49/// let mut runner2 = Runner::new(prog, saved_vars);
50/// runner2.restore(saved_snap).unwrap();
51/// // runner2 is back at the beginning of node "A" with $gold already set.
52/// # }
53/// ```
54#[derive(Debug, Clone, PartialEq, Eq)]
55#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
56pub struct RunnerSnapshot {
57    /// The node that was executing when the snapshot was taken.
58    ///
59    /// When restoring, [`Runner::restore`](crate::runtime::Runner::restore) will
60    /// restart execution from the *beginning* of this node. This is intentional:
61    /// the in-progress statement list is not serialised because it would
62    /// require the full AST to be round-tripped.
63    pub current_node: Option<String>,
64
65    /// How many times each node has been visited.
66    pub visits: HashMap<String, u32>,
67
68    /// IDs of `<<once>>` blocks (and once-line-variants) that have already
69    /// fired and must not fire again.
70    pub once_seen: HashSet<String>,
71}