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}