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}