Skip to main content

zapcode_core/
snapshot.rs

1use std::collections::{HashMap, HashSet};
2
3use serde::{Deserialize, Serialize};
4
5use crate::compiler::CompiledProgram;
6use crate::error::{Result, ZapcodeError};
7use crate::sandbox::ResourceLimits;
8use crate::value::Value;
9use crate::vm::{CallFrame, TryInfo, Vm, VmState};
10
11/// Internal serializable representation of VM state at a suspension point.
12#[derive(Debug, Clone, Serialize, Deserialize)]
13struct VmSnapshot {
14    program: CompiledProgram,
15    stack: Vec<Value>,
16    frames: Vec<CallFrame>,
17    /// User-defined globals only — builtins are re-registered on resume.
18    globals: Vec<(String, Value)>,
19    try_stack: Vec<TryInfo>,
20    stdout: String,
21    limits: ResourceLimits,
22    external_functions: Vec<String>,
23}
24
25/// A snapshot of VM state at a suspension point.
26/// Can be serialized to bytes and resumed later in any process.
27#[derive(Debug, Clone, Serialize, Deserialize)]
28pub struct ZapcodeSnapshot {
29    data: Vec<u8>,
30}
31
32impl ZapcodeSnapshot {
33    /// Capture the current VM state as a snapshot.
34    pub(crate) fn capture(vm: &Vm) -> Result<Self> {
35        // Filter out builtin globals — they'll be re-registered on resume.
36        let builtin_names: HashSet<&str> = Vm::BUILTIN_GLOBAL_NAMES.iter().copied().collect();
37        let user_globals: Vec<(String, Value)> = vm
38            .globals
39            .iter()
40            .filter(|(k, _)| !builtin_names.contains(k.as_str()))
41            .map(|(k, v)| (k.clone(), v.clone()))
42            .collect();
43
44        let snapshot = VmSnapshot {
45            program: vm.program.clone(),
46            stack: vm.stack.clone(),
47            frames: vm.frames.clone(),
48            globals: user_globals,
49            try_stack: vm.try_stack.clone(),
50            stdout: vm.stdout.clone(),
51            limits: vm.limits.clone(),
52            external_functions: vm.external_functions.iter().cloned().collect(),
53        };
54
55        let data = postcard::to_allocvec(&snapshot)
56            .map_err(|e| ZapcodeError::SnapshotError(format!("capture failed: {}", e)))?;
57
58        Ok(Self { data })
59    }
60
61    /// Serialize the snapshot to bytes for storage / transport.
62    pub fn dump(&self) -> Result<Vec<u8>> {
63        postcard::to_allocvec(self)
64            .map_err(|e| ZapcodeError::SnapshotError(format!("dump failed: {}", e)))
65    }
66
67    /// Deserialize a snapshot from bytes.
68    pub fn load(bytes: &[u8]) -> Result<Self> {
69        postcard::from_bytes(bytes)
70            .map_err(|e| ZapcodeError::SnapshotError(format!("load failed: {}", e)))
71    }
72
73    /// Resume execution with a return value from the external function.
74    /// Returns a `VmState` which may be `Complete` or another `Suspended`.
75    pub fn resume(self, return_value: Value) -> Result<VmState> {
76        let vm_snap: VmSnapshot = postcard::from_bytes(&self.data)
77            .map_err(|e| ZapcodeError::SnapshotError(format!("resume decode failed: {}", e)))?;
78
79        let user_globals: HashMap<String, Value> = vm_snap.globals.into_iter().collect();
80        let ext_set: HashSet<String> = vm_snap.external_functions.into_iter().collect();
81
82        let mut vm = Vm::from_snapshot(
83            vm_snap.program,
84            vm_snap.stack,
85            vm_snap.frames,
86            user_globals,
87            vm_snap.try_stack,
88            vm_snap.stdout,
89            vm_snap.limits,
90            ext_set,
91        );
92
93        // Push the return value onto the stack — this is the result the
94        // `CallExternal` instruction was waiting for.
95        vm.stack.push(return_value);
96
97        vm.resume_execution()
98    }
99}