Skip to main content

navi_vm\snapshot/
types.rs

1//! Snapshot data types for serializing VM state.
2
3use std::collections::VecDeque;
4
5use fixedbitset::FixedBitSet;
6use navi_program::{Program, TypeDescriptor};
7use serde::{Deserialize, Deserializer, Serialize, Serializer};
8
9use crate::{
10    ExecutionLimits, InputSessions, LastInfo, SymbolInfo, TimeFrame,
11    events::Event,
12    gc_serde::{SerializedObject, SerializedRawValue},
13    script_info::ScriptInfo,
14    strategy::StrategyState,
15    visuals::{Chart, Graph, GraphId},
16};
17
18/// Current snapshot format version.
19///
20/// Bump this whenever the snapshot format changes. There is no backward
21/// compatibility — a version mismatch is a hard error.
22pub(crate) const SNAPSHOT_VERSION: u32 = 4;
23
24/// Errors that can occur during snapshot save/restore.
25#[derive(Debug, thiserror::Error)]
26#[non_exhaustive]
27pub enum SnapshotError {
28    /// postcard encoding failed.
29    #[error("snapshot encode error: {0}")]
30    Encode(String),
31
32    /// postcard decoding failed.
33    #[error("snapshot decode error: {0}")]
34    Decode(String),
35
36    /// The snapshot was created with a different format version.
37    #[error("snapshot version mismatch: expected {expected}, got {got}")]
38    VersionMismatch {
39        /// The version this build expects.
40        expected: u32,
41        /// The version found in the snapshot data.
42        got: u32,
43    },
44
45    /// A serialized object reference points outside the object table.
46    #[error("invalid object reference: index {0} out of bounds")]
47    InvalidReference(u32),
48
49    /// A GC object could not be reconstructed (e.g. unknown TypeDescriptor).
50    #[error("object restore error: {0}")]
51    RestoreError(String),
52
53    /// The current realtime bar has not been confirmed before saving.
54    ///
55    /// Call `Instance::execute` with
56    /// `ExecuteMode::Confirm` before saving.
57    #[error("cannot save snapshot: current bar has not been confirmed")]
58    UnconfirmedBar,
59}
60
61/// A serialized variable value.
62#[derive(Debug, Clone, Serialize, Deserialize)]
63#[serde(rename_all = "camelCase")]
64pub(crate) enum SerializedVariableValue {
65    /// A per-bar series with offset tracking.
66    Series {
67        values: VecDeque<SerializedRawValue>,
68        offset: usize,
69        length: usize,
70        max_bars_back: Option<usize>,
71        /// The maximum lookback depth observed at runtime.
72        /// Defaults to `usize::MAX` for backward compatibility with older
73        /// snapshots (no optimization applied until re-observed).
74        #[serde(default = "default_observed_max_lookback")]
75        observed_max_lookback: usize,
76    },
77    /// A single (non-series) value.
78    Simple(SerializedRawValue),
79}
80
81fn default_observed_max_lookback() -> usize {
82    usize::MAX
83}
84
85/// A serialized input value.
86#[derive(Debug, Clone, Serialize, Deserialize)]
87#[serde(rename_all = "camelCase")]
88pub(crate) struct SerializedInputValue {
89    pub(crate) value: SerializedRawValue,
90    pub(crate) is_reference_type: bool,
91    pub(crate) value_type: TypeDescriptor,
92}
93
94/// A serialized rollback action.
95#[derive(Debug, Clone, Serialize, Deserialize)]
96#[serde(rename_all = "camelCase")]
97pub(crate) enum SerializedRollbackAction {
98    Var {
99        var_id: usize,
100        value: SerializedRawValue,
101        is_reference_type: bool,
102    },
103    UdtField {
104        udt: SerializedRawValue,
105        field_id: usize,
106        value: SerializedRawValue,
107        is_reference_type: bool,
108    },
109    RemoveGraph {
110        id: GraphId,
111    },
112    RestoreGraph {
113        id: GraphId,
114        graph: Graph,
115    },
116}
117
118/// Borrowing view of an [`Instance`](crate::Instance)'s state, used during
119/// [`save_state`](crate::Instance::save_state) to avoid cloning heavy types.
120#[derive(Serialize)]
121#[serde(rename_all = "camelCase")]
122pub(crate) struct InstanceSnapshotRef<'a> {
123    pub(crate) version: u32,
124    pub(crate) program: &'a Program,
125
126    pub(crate) bar_index: usize,
127    pub(crate) input_index: usize,
128    pub(crate) last_info: Option<LastInfo>,
129
130    pub(crate) last_bar_time: i64,
131    pub(crate) chart: &'a Chart,
132    pub(crate) events: &'a Vec<Event>,
133
134    pub(crate) strategy_state: &'a Option<Box<StrategyState>>,
135
136    pub(crate) object_table: Vec<SerializedObject>,
137
138    pub(crate) variables: Vec<SerializedVariableValue>,
139    pub(crate) inputs: Vec<SerializedInputValue>,
140    pub(crate) rollback_actions: Vec<SerializedRollbackAction>,
141    #[serde(serialize_with = "serialize_fixedbitset")]
142    pub(crate) var_initialized: &'a FixedBitSet,
143
144    pub(crate) timeframe: TimeFrame,
145    pub(crate) symbol_info: &'a SymbolInfo,
146    pub(crate) script_info: &'a ScriptInfo,
147    pub(crate) input_sessions: InputSessions,
148    pub(crate) execution_limits: ExecutionLimits,
149}
150
151/// Owned snapshot produced by deserializing the bytes from `save_state`.
152#[derive(Deserialize)]
153#[serde(rename_all = "camelCase")]
154pub(crate) struct InstanceSnapshot {
155    pub(crate) version: u32,
156    pub(crate) program: Program,
157
158    pub(crate) bar_index: usize,
159    pub(crate) input_index: usize,
160    pub(crate) last_info: Option<LastInfo>,
161
162    pub(crate) last_bar_time: i64,
163    pub(crate) chart: Chart,
164    pub(crate) events: Vec<Event>,
165
166    pub(crate) strategy_state: Option<Box<StrategyState>>,
167
168    pub(crate) object_table: Vec<SerializedObject>,
169
170    pub(crate) variables: Vec<SerializedVariableValue>,
171    pub(crate) inputs: Vec<SerializedInputValue>,
172    pub(crate) rollback_actions: Vec<SerializedRollbackAction>,
173    #[serde(deserialize_with = "deserialize_fixedbitset")]
174    pub(crate) var_initialized: FixedBitSet,
175
176    pub(crate) timeframe: TimeFrame,
177    pub(crate) symbol_info: SymbolInfo,
178    pub(crate) script_info: ScriptInfo,
179    pub(crate) input_sessions: InputSessions,
180    pub(crate) execution_limits: ExecutionLimits,
181}
182
183/// Serializes a [`FixedBitSet`] as `[capacity, block0, block1, …]` using
184/// `u64` elements — avoids the zero-copy `&[u8]` in `fixedbitset`'s own serde
185/// which is incompatible with non-self-describing formats like postcard.
186fn serialize_fixedbitset<S: Serializer>(fbs: &FixedBitSet, ser: S) -> Result<S::Ok, S::Error> {
187    use serde::ser::SerializeSeq;
188    let blocks = fbs.as_slice();
189    let mut seq = ser.serialize_seq(Some(1 + blocks.len()))?;
190    seq.serialize_element(&(fbs.len() as u64))?;
191    for &block in blocks {
192        seq.serialize_element(&(block as u64))?;
193    }
194    seq.end()
195}
196
197/// Deserializes a [`FixedBitSet`] from the `[capacity, block0, block1, …]`
198/// sequence written by [`serialize_fixedbitset`].
199fn deserialize_fixedbitset<'de, D: Deserializer<'de>>(de: D) -> Result<FixedBitSet, D::Error> {
200    use serde::de::{SeqAccess, Visitor};
201    struct FbsVisitor;
202    impl<'de> Visitor<'de> for FbsVisitor {
203        type Value = FixedBitSet;
204        fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
205            f.write_str("a sequence [capacity, block…]")
206        }
207        fn visit_seq<A: SeqAccess<'de>>(self, mut seq: A) -> Result<FixedBitSet, A::Error> {
208            use serde::de::Error;
209            let capacity: u64 = seq
210                .next_element()?
211                .ok_or_else(|| A::Error::invalid_length(0, &self))?;
212            let mut blocks = Vec::new();
213            while let Some(b) = seq.next_element::<u64>()? {
214                blocks.push(b as usize);
215            }
216            Ok(FixedBitSet::with_capacity_and_blocks(
217                capacity as usize,
218                blocks,
219            ))
220        }
221    }
222    de.deserialize_seq(FbsVisitor)
223}