Skip to main content

navi_vm\snapshot/
save.rs

1//! `Instance::save_state` implementation.
2
3use navi_program::{TypeDescriptor, VariableFlags};
4use navi_visitor::PersistMode;
5
6use crate::{
7    Instance,
8    gc_serde::{ObjectCollector, SerializedObject, SerializedRawValue},
9    objects::{NaviRef, udt::NaviUdt},
10    raw_value::{FromRawValue, RawValue},
11    rollback::RollbackAction,
12    snapshot::types::{
13        InstanceSnapshotRef, SNAPSHOT_VERSION, SerializedInputValue, SerializedRollbackAction,
14        SerializedVariableValue, SnapshotError,
15    },
16    state::{ArenaType, VariableValue},
17};
18
19/// Serializes a single [`RawValue`] from an arena into an arena-independent
20/// form.
21///
22/// Returns the serialized value and the object table needed to restore it.
23pub(crate) fn serialize_value_in_arena(
24    arena: &mut ArenaType,
25    raw: RawValue,
26    is_reference_type: bool,
27    value_type: &TypeDescriptor,
28) -> (SerializedRawValue, Vec<SerializedObject>) {
29    let mut collector = ObjectCollector::new();
30    let serialized =
31        arena.mutate(|_mc, _| collector.serialize_raw_value(raw, is_reference_type, value_type));
32    (serialized, collector.objects)
33}
34
35impl Instance {
36    /// Serializes the current VM state to a binary blob.
37    ///
38    /// The returned bytes encode the full program, all variables, inputs,
39    /// GC-heap objects, chart, strategy state, and configuration. Pass them
40    /// to [`Instance::restore_state`] to resume execution.
41    ///
42    /// The current realtime bar **must** be confirmed before calling this
43    /// method (e.g. by executing `ExecuteMode::Confirm`).
44    /// Returns [`SnapshotError::UnconfirmedBar`](super::types::SnapshotError::UnconfirmedBar) otherwise.
45    ///
46    /// Candlestick data is **not** included in the snapshot. When [`run()`]
47    /// is called on a restored instance, the VM automatically passes
48    /// `last_bar_time + 1` to the data provider.
49    ///
50    /// [`run()`]: crate::Instance::run
51    ///
52    /// # Errors
53    ///
54    /// Returns [`SnapshotError::UnconfirmedBar`](super::types::SnapshotError::UnconfirmedBar) if the current bar has not
55    /// been confirmed, or
56    /// [`SnapshotError::Encode`](super::types::SnapshotError::Encode) if
57    /// postcard serialization fails.
58    pub fn save_state(&mut self) -> Result<Vec<u8>, SnapshotError> {
59        if !self.last_bar_confirmed {
60            return Err(SnapshotError::UnconfirmedBar);
61        }
62        let mut collector = ObjectCollector::new();
63        let program = &self.program;
64        let var_infos = program.variables();
65
66        // Serialize arena state inside `mutate_root` so all GC pointers are
67        // guaranteed live (no collection can run during this callback).
68        let (variables, inputs, rollback_actions, var_initialized) =
69            self.arena.mutate_root(|_mc, state| {
70                let variables: Vec<SerializedVariableValue> = var_infos
71                    .iter()
72                    .zip(state.variables.iter())
73                    .map(|(vi, vv)| {
74                        let is_ref = vi.flags.contains(VariableFlags::REFERENCE_TYPE);
75                        let should_walk = is_ref
76                            && matches!(
77                                vi.persist_mode,
78                                PersistMode::Persist | PersistMode::IntrabarPersist
79                            );
80                        match vv {
81                            VariableValue::Series(sv) => {
82                                let values = sv
83                                    .values
84                                    .iter()
85                                    .map(|raw| {
86                                        if should_walk {
87                                            collector.serialize_raw_value(
88                                                *raw,
89                                                is_ref,
90                                                &vi.value_type,
91                                            )
92                                        } else if is_ref {
93                                            // NoPersist reference — save as NA
94                                            SerializedRawValue::Scalar(f64::NAN)
95                                        } else {
96                                            SerializedRawValue::Scalar(f64::from_raw_value(*raw))
97                                        }
98                                    })
99                                    .collect();
100                                SerializedVariableValue::Series {
101                                    values,
102                                    offset: sv.offset,
103                                    length: sv.length,
104                                    max_bars_back: sv.max_bars_back,
105                                    observed_max_lookback: sv.observed_max_lookback,
106                                }
107                            }
108                            VariableValue::Simple(raw) => {
109                                let v = if should_walk {
110                                    collector.serialize_raw_value(*raw, is_ref, &vi.value_type)
111                                } else if is_ref {
112                                    SerializedRawValue::Scalar(f64::NAN)
113                                } else {
114                                    SerializedRawValue::Scalar(f64::from_raw_value(*raw))
115                                };
116                                SerializedVariableValue::Simple(v)
117                            }
118                        }
119                    })
120                    .collect();
121
122                let inputs: Vec<SerializedInputValue> = state
123                    .inputs
124                    .iter()
125                    .map(|iv| SerializedInputValue {
126                        value: collector.serialize_raw_value(
127                            iv.value,
128                            iv.is_reference_type,
129                            &iv.value_type,
130                        ),
131                        is_reference_type: iv.is_reference_type,
132                        value_type: iv.value_type.clone(),
133                    })
134                    .collect();
135
136                let rollback_actions: Vec<SerializedRollbackAction> = state
137                    .rollback_actions
138                    .iter()
139                    .map(|ra| match ra {
140                        RollbackAction::Var {
141                            var_id,
142                            value,
143                            is_reference_type,
144                        } => {
145                            let vt = &var_infos[*var_id].value_type;
146                            SerializedRollbackAction::Var {
147                                var_id: *var_id,
148                                value: collector.serialize_raw_value(
149                                    *value,
150                                    *is_reference_type,
151                                    vt,
152                                ),
153                                is_reference_type: *is_reference_type,
154                            }
155                        }
156                        RollbackAction::UdtField {
157                            udt,
158                            field_id,
159                            value,
160                            is_reference_type,
161                        } => {
162                            let udt_ref = NaviRef::<NaviUdt>::from_raw_value(*udt);
163                            let fi = &udt_ref.object_info().fields[*field_id];
164                            // Intern the UDT object itself. We use
165                            // Object(0) as a dummy type — the collector
166                            // deduplicates by pointer address.
167                            let udt_sv = collector.serialize_raw_value(
168                                *udt,
169                                true,
170                                &TypeDescriptor::object(0),
171                            );
172                            let val_sv = collector.serialize_raw_value(
173                                *value,
174                                *is_reference_type,
175                                &fi.value_type,
176                            );
177                            SerializedRollbackAction::UdtField {
178                                udt: udt_sv,
179                                field_id: *field_id,
180                                value: val_sv,
181                                is_reference_type: *is_reference_type,
182                            }
183                        }
184                        RollbackAction::RemoveGraph { id } => {
185                            SerializedRollbackAction::RemoveGraph { id: *id }
186                        }
187                        RollbackAction::RestoreGraph { id, graph } => {
188                            SerializedRollbackAction::RestoreGraph {
189                                id: *id,
190                                graph: graph.clone(),
191                            }
192                        }
193                    })
194                    .collect();
195
196                (
197                    variables,
198                    inputs,
199                    rollback_actions,
200                    state.var_initialized.clone(),
201                )
202            });
203
204        // Assemble the borrowing snapshot and encode.
205        let snapshot = InstanceSnapshotRef {
206            version: SNAPSHOT_VERSION,
207            program: &self.program,
208            bar_index: self.bar_index,
209            input_index: self.input_index,
210            last_info: self.last_info,
211            last_bar_time: self.candlesticks.last().map(|c| c.time).unwrap_or(0),
212            chart: &self.chart,
213            events: &self.events,
214            strategy_state: &self.strategy_state,
215            object_table: collector.objects,
216            variables,
217            inputs,
218            rollback_actions,
219            var_initialized: &var_initialized,
220            timeframe: self.timeframe,
221            symbol_info: &self.symbol_info,
222            script_info: self.script_info.as_ref(),
223            input_sessions: self.input_sessions,
224            execution_limits: self.execution_limits,
225        };
226
227        postcard::to_allocvec(&snapshot).map_err(|e| SnapshotError::Encode(e.to_string()))
228    }
229}