openpine_vm/snapshot/save.rs
1//! `Instance::save_state` implementation.
2
3use openpine_compiler::{instructions::TypeDescriptor, program::VariableFlags};
4use openpine_visitor::PersistMode;
5
6use crate::{
7 Instance,
8 gc_serde::{ObjectCollector, SerializedObject, SerializedRawValue},
9 objects::{PineRef, udt::PineUdt},
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 /// # Errors
43 ///
44 /// Returns [`SnapshotError::Encode`] if bincode serialization fails.
45 pub fn save_state(&mut self) -> Result<Vec<u8>, SnapshotError> {
46 let mut collector = ObjectCollector::new();
47 let program = &self.program;
48 let var_infos = program.variables();
49
50 // Serialize arena state inside `mutate_root` so all GC pointers are
51 // guaranteed live (no collection can run during this callback).
52 let (variables, inputs, rollback_actions, var_initialized) =
53 self.arena.mutate_root(|_mc, state| {
54 let variables: Vec<SerializedVariableValue> = var_infos
55 .iter()
56 .zip(state.variables.iter())
57 .map(|(vi, vv)| {
58 let is_ref = vi.flags.contains(VariableFlags::REFERENCE_TYPE);
59 let should_walk = is_ref
60 && matches!(
61 vi.persist_mode,
62 PersistMode::Persist | PersistMode::IntrabarPersist
63 );
64 match vv {
65 VariableValue::Series(sv) => {
66 let values = sv
67 .values
68 .iter()
69 .map(|raw| {
70 if should_walk {
71 collector.serialize_raw_value(
72 *raw,
73 is_ref,
74 &vi.value_type,
75 )
76 } else if is_ref {
77 // NoPersist reference — save as NA
78 SerializedRawValue::Scalar(f64::NAN)
79 } else {
80 SerializedRawValue::Scalar(f64::from_raw_value(*raw))
81 }
82 })
83 .collect();
84 SerializedVariableValue::Series {
85 values,
86 offset: sv.offset,
87 max_length: sv.max_length,
88 }
89 }
90 VariableValue::Simple(raw) => {
91 let v = if should_walk {
92 collector.serialize_raw_value(*raw, is_ref, &vi.value_type)
93 } else if is_ref {
94 SerializedRawValue::Scalar(f64::NAN)
95 } else {
96 SerializedRawValue::Scalar(f64::from_raw_value(*raw))
97 };
98 SerializedVariableValue::Simple(v)
99 }
100 }
101 })
102 .collect();
103
104 let inputs: Vec<SerializedInputValue> = state
105 .inputs
106 .iter()
107 .map(|iv| SerializedInputValue {
108 value: collector.serialize_raw_value(
109 iv.value,
110 iv.is_reference_type,
111 &iv.value_type,
112 ),
113 is_reference_type: iv.is_reference_type,
114 value_type: iv.value_type.clone(),
115 })
116 .collect();
117
118 let rollback_actions: Vec<SerializedRollbackAction> = state
119 .rollback_actions
120 .iter()
121 .map(|ra| match ra {
122 RollbackAction::Var {
123 var_id,
124 value,
125 is_reference_type,
126 } => {
127 let vt = &var_infos[*var_id].value_type;
128 SerializedRollbackAction::Var {
129 var_id: *var_id,
130 value: collector.serialize_raw_value(
131 *value,
132 *is_reference_type,
133 vt,
134 ),
135 is_reference_type: *is_reference_type,
136 }
137 }
138 RollbackAction::UdtField {
139 udt,
140 field_id,
141 value,
142 is_reference_type,
143 } => {
144 let udt_ref = PineRef::<PineUdt>::from_raw_value(*udt);
145 let fi = &udt_ref.object_info().fields[*field_id];
146 // Intern the UDT object itself. We use
147 // Object(0) as a dummy type — the collector
148 // deduplicates by pointer address.
149 let udt_sv = collector.serialize_raw_value(
150 *udt,
151 true,
152 &TypeDescriptor::object(0),
153 );
154 let val_sv = collector.serialize_raw_value(
155 *value,
156 *is_reference_type,
157 &fi.value_type,
158 );
159 SerializedRollbackAction::UdtField {
160 udt: udt_sv,
161 field_id: *field_id,
162 value: val_sv,
163 is_reference_type: *is_reference_type,
164 }
165 }
166 RollbackAction::RemoveGraph { id } => {
167 SerializedRollbackAction::RemoveGraph { id: *id }
168 }
169 RollbackAction::RestoreGraph { id, graph } => {
170 SerializedRollbackAction::RestoreGraph {
171 id: *id,
172 graph: graph.clone(),
173 }
174 }
175 })
176 .collect();
177
178 (
179 variables,
180 inputs,
181 rollback_actions,
182 state.var_initialized.clone(),
183 )
184 });
185
186 // Assemble the borrowing snapshot and encode.
187 let snapshot = InstanceSnapshotRef {
188 version: SNAPSHOT_VERSION,
189 program: &self.program,
190 bar_index: self.bar_index,
191 input_index: self.input_index,
192 last_info: self.last_info,
193 candlesticks: &self.candlesticks,
194 chart: &self.chart,
195 events: &self.events,
196 strategy_state: &self.strategy_state,
197 object_table: collector.objects,
198 variables,
199 inputs,
200 rollback_actions,
201 var_initialized: &var_initialized,
202 timeframe: self.timeframe,
203 symbol_info: &self.symbol_info,
204 script_info: &self.script_info,
205 input_sessions: self.input_sessions,
206 execution_limits: self.execution_limits,
207 };
208
209 let config = bincode::config::standard();
210 bincode::serde::encode_to_vec(&snapshot, config)
211 .map_err(|e| SnapshotError::Encode(e.to_string()))
212 }
213}