Skip to main content

rs_teststand/execution/
sequence_context.rs

1//! The variable scopes and run state visible from a point in a running sequence.
2
3use rs_teststand_sys::Dispatch;
4
5use crate::Error;
6use crate::dispids::sequence_context;
7
8/// A point in a running sequence, and everything reachable from it
9/// (`SequenceContext`).
10///
11/// This is what an expression calls `ThisContext`, obtained from
12/// [`Thread::get_sequence_context`](crate::Thread::get_sequence_context). It is
13/// the door to the variable scopes, `Locals`, `Parameters`, `FileGlobals`,
14/// `StationGlobals`, and to `RunState`.
15///
16/// # Lifetimes: what may outlive the run, and what may not
17///
18/// A host holding the wrong reference after an execution ends keeps a COM
19/// object alive on a finished run. The documentation draws the line explicitly:
20/// these exist before, and persist after, the execution, ///
21/// * [`station_globals`](Self::station_globals), shared by the whole session,
22///   and modifications from one execution are seen by all of them
23/// * [`sequence_file`](Self::sequence_file)
24///
25///, while everything else belongs to the run, [`file_globals`](Self::file_globals)
26/// most notably: it is the execution's own copy, and writing to it does not
27/// touch the file's stored defaults.
28///
29/// So the safe pattern for a host is to read what it needs from a context while
30/// the run is alive and keep the *data*, not the reference. Values that must
31/// outlive the run come from the engine or the file instead:
32/// [`Engine::globals`](crate::Engine::globals) for station globals with no
33/// execution at all, and
34/// [`SequenceFile::file_globals_default_values`](crate::SequenceFile::file_globals_default_values)
35/// for the file's edit-time defaults.
36#[derive(Debug)]
37pub struct SequenceContext {
38    dispatch: Box<dyn Dispatch>,
39}
40
41impl SequenceContext {
42    /// Wraps a dispatch handle returned by the engine.
43    pub(crate) fn new(dispatch: Box<dyn Dispatch>) -> Self {
44        Self { dispatch }
45    }
46
47    /// The context as a property tree (`SequenceContext.AsPropertyObject`).
48    ///
49    /// # Errors
50    /// [`Error`] if the COM call fails or returns an unexpected type.
51    pub fn as_property_object(&self) -> Result<crate::PropertyObject, Error> {
52        Ok(crate::PropertyObject::new(
53            self.dispatch
54                .call(sequence_context::AS_PROPERTY_OBJECT, &[])?
55                .into_object()?,
56        ))
57    }
58
59    /// The running sequence's local variables (`SequenceContext.Locals`).
60    ///
61    /// The run's own copy, it does not outlive the execution.
62    ///
63    /// # Errors
64    /// [`Error`] if the COM call fails or returns an unexpected type.
65    pub fn locals(&self) -> Result<crate::PropertyObject, Error> {
66        Ok(crate::PropertyObject::new(
67            self.dispatch.get(sequence_context::LOCALS)?.into_object()?,
68        ))
69    }
70
71    /// The running sequence's parameters (`SequenceContext.Parameters`).
72    ///
73    /// # Errors
74    /// [`Error`] if the COM call fails or returns an unexpected type.
75    pub fn parameters(&self) -> Result<crate::PropertyObject, Error> {
76        Ok(crate::PropertyObject::new(
77            self.dispatch
78                .get(sequence_context::PARAMETERS)?
79                .into_object()?,
80        ))
81    }
82
83    /// The executing file's globals (`SequenceContext.FileGlobals`).
84    ///
85    /// **Belongs to the run.** This is the execution's own copy: changes here
86    /// do not affect the defaults stored in the sequence file, and the object
87    /// is meaningless once the run is over. Read what is needed now, or use
88    /// [`SequenceFile::file_globals_default_values`](crate::SequenceFile::file_globals_default_values)
89    /// for the values that live in the file.
90    ///
91    /// # Errors
92    /// [`Error`] if the COM call fails or returns an unexpected type.
93    pub fn file_globals(&self) -> Result<crate::PropertyObject, Error> {
94        Ok(crate::PropertyObject::new(
95            self.dispatch
96                .get(sequence_context::FILE_GLOBALS)?
97                .into_object()?,
98        ))
99    }
100
101    /// The station's global variables (`SequenceContext.StationGlobals`).
102    ///
103    /// **Outlives the run**, and is shared: a change made here is seen by every
104    /// execution in the session, and persists on disk. Reachable without any
105    /// execution through [`Engine::globals`](crate::Engine::globals), which is
106    /// the better route when no run is involved.
107    ///
108    /// # Errors
109    /// [`Error`] if the COM call fails or returns an unexpected type.
110    pub fn station_globals(&self) -> Result<crate::PropertyObject, Error> {
111        Ok(crate::PropertyObject::new(
112            self.dispatch
113                .get(sequence_context::STATION_GLOBALS)?
114                .into_object()?,
115        ))
116    }
117
118    /// The execution this context belongs to (`SequenceContext.Execution`).
119    ///
120    /// # Errors
121    /// [`Error`] if the COM call fails or returns an unexpected type.
122    pub fn execution(&self) -> Result<crate::Execution, Error> {
123        Ok(crate::Execution::new(
124            self.dispatch
125                .get(sequence_context::EXECUTION)?
126                .into_object()?,
127        ))
128    }
129
130    /// The thread this context belongs to (`SequenceContext.Thread`).
131    ///
132    /// # Errors
133    /// [`Error`] if the COM call fails or returns an unexpected type.
134    pub fn thread(&self) -> Result<crate::Thread, Error> {
135        Ok(crate::Thread::new(
136            self.dispatch.get(sequence_context::THREAD)?.into_object()?,
137        ))
138    }
139
140    /// The file the running sequence came from (`SequenceContext.SequenceFile`).
141    ///
142    /// **Outlives the run**, it is the loaded file, not a copy.
143    ///
144    /// # Errors
145    /// [`Error`] if the COM call fails or returns an unexpected type.
146    pub fn sequence_file(&self) -> Result<crate::SequenceFile, Error> {
147        Ok(crate::SequenceFile::new(
148            self.dispatch
149                .get(sequence_context::SEQUENCE_FILE)?
150                .into_object()?,
151        ))
152    }
153
154    /// The sequence being run (`SequenceContext.Sequence`).
155    ///
156    /// # Errors
157    /// [`Error`] if the COM call fails or returns an unexpected type.
158    pub fn sequence(&self) -> Result<crate::Sequence, Error> {
159        Ok(crate::Sequence::new(
160            self.dispatch
161                .get(sequence_context::SEQUENCE)?
162                .into_object()?,
163        ))
164    }
165
166    /// How deep this frame sits in the call stack
167    /// (`SequenceContext.CallStackDepth`).
168    ///
169    /// # Errors
170    /// [`Error`] if the COM call fails or returns an unexpected type.
171    pub fn call_stack_depth(&self) -> Result<i32, Error> {
172        Ok(self
173            .dispatch
174            .get(sequence_context::CALL_STACK_DEPTH)?
175            .as_i32()?)
176    }
177
178    /// The index of the step being run (`SequenceContext.StepIndex`).
179    ///
180    /// # Errors
181    /// [`Error`] if the COM call fails or returns an unexpected type.
182    pub fn step_index(&self) -> Result<i32, Error> {
183        Ok(self.dispatch.get(sequence_context::STEP_INDEX)?.as_i32()?)
184    }
185
186    /// How many steps have run so far (`SequenceContext.NumStepsExecuted`).
187    ///
188    /// # Errors
189    /// [`Error`] if the COM call fails or returns an unexpected type.
190    pub fn num_steps_executed(&self) -> Result<i32, Error> {
191        Ok(self
192            .dispatch
193            .get(sequence_context::NUM_STEPS_EXECUTED)?
194            .as_i32()?)
195    }
196
197    /// Whether the sequence has failed (`SequenceContext.SequenceFailed`).
198    ///
199    /// # Errors
200    /// [`Error`] if the COM call fails or returns an unexpected type.
201    pub fn sequence_failed(&self) -> Result<bool, Error> {
202        Ok(self
203            .dispatch
204            .get(sequence_context::SEQUENCE_FAILED)?
205            .as_bool()?)
206    }
207
208    /// Whether a run-time error has been reported
209    /// (`SequenceContext.ErrorReported`).
210    ///
211    /// # Errors
212    /// [`Error`] if the COM call fails or returns an unexpected type.
213    pub fn error_reported(&self) -> Result<bool, Error> {
214        Ok(self
215            .dispatch
216            .get(sequence_context::ERROR_REPORTED)?
217            .as_bool()?)
218    }
219
220    /// The current run-time error message
221    /// (`SequenceContext.RunTimeErrorMessage`).
222    ///
223    /// # Errors
224    /// [`Error`] if the COM call fails or returns an unexpected type.
225    pub fn run_time_error_message(&self) -> Result<String, Error> {
226        Ok(self
227            .dispatch
228            .get(sequence_context::RUN_TIME_ERROR_MESSAGE)?
229            .into_string()?)
230    }
231
232    /// The step being run right now (`SequenceContext.Step`).
233    ///
234    /// **Fallible by nature.** The engine documents this property as existing
235    /// only while a step executes, between steps, and at a breakpoint, there
236    /// is no current step. An error here is a legitimate state, not a defect,
237    /// which is why it is not reported as an empty value.
238    ///
239    /// # Errors
240    /// [`Error`] if no step is executing, or the COM call fails.
241    pub fn step(&self) -> Result<crate::Step, Error> {
242        Ok(crate::Step::new(
243            self.dispatch.get(sequence_context::STEP)?.into_object()?,
244        ))
245    }
246}