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 runtime state tree a running sequence sees as `RunState`.
119    ///
120    /// Not a COM member: `RunState` carries no DISPID in any supported type
121    /// library, it is a node in the context's property tree, which is why this
122    /// goes through the property object rather than a dispatch call. The vendor
123    /// reaches it the same way, by lookup string.
124    ///
125    /// Under it sit the things a host reports on that have no member of their
126    /// own, notably `TestSockets` for a multi-UUT panel, alongside duplicates
127    /// of members this type already exposes directly, such as `Sequence` and
128    /// `CallStackDepth`. Prefer the direct accessors where they exist, and
129    /// reach through here for the rest.
130    ///
131    /// # Errors
132    /// [`Error`] if the context carries no `RunState` or a COM call fails.
133    pub fn run_state(&self) -> Result<crate::PropertyObject, Error> {
134        self.as_property_object()?
135            .get_property_object("RunState", crate::property::PropertyOptions::NONE.bits())
136    }
137
138    /// The execution this context belongs to (`SequenceContext.Execution`).
139    ///
140    /// # Errors
141    /// [`Error`] if the COM call fails or returns an unexpected type.
142    pub fn execution(&self) -> Result<crate::Execution, Error> {
143        Ok(crate::Execution::new(
144            self.dispatch
145                .get(sequence_context::EXECUTION)?
146                .into_object()?,
147        ))
148    }
149
150    /// The thread this context belongs to (`SequenceContext.Thread`).
151    ///
152    /// # Errors
153    /// [`Error`] if the COM call fails or returns an unexpected type.
154    pub fn thread(&self) -> Result<crate::Thread, Error> {
155        Ok(crate::Thread::new(
156            self.dispatch.get(sequence_context::THREAD)?.into_object()?,
157        ))
158    }
159
160    /// The file the running sequence came from (`SequenceContext.SequenceFile`).
161    ///
162    /// **Outlives the run**, it is the loaded file, not a copy.
163    ///
164    /// # Errors
165    /// [`Error`] if the COM call fails or returns an unexpected type.
166    pub fn sequence_file(&self) -> Result<crate::SequenceFile, Error> {
167        Ok(crate::SequenceFile::new(
168            self.dispatch
169                .get(sequence_context::SEQUENCE_FILE)?
170                .into_object()?,
171        ))
172    }
173
174    /// The sequence being run (`SequenceContext.Sequence`).
175    ///
176    /// # Errors
177    /// [`Error`] if the COM call fails or returns an unexpected type.
178    pub fn sequence(&self) -> Result<crate::Sequence, Error> {
179        Ok(crate::Sequence::new(
180            self.dispatch
181                .get(sequence_context::SEQUENCE)?
182                .into_object()?,
183        ))
184    }
185
186    /// How deep this frame sits in the call stack
187    /// (`SequenceContext.CallStackDepth`).
188    ///
189    /// # Errors
190    /// [`Error`] if the COM call fails or returns an unexpected type.
191    pub fn call_stack_depth(&self) -> Result<i32, Error> {
192        Ok(self
193            .dispatch
194            .get(sequence_context::CALL_STACK_DEPTH)?
195            .as_i32()?)
196    }
197
198    /// The index of the step being run (`SequenceContext.StepIndex`).
199    ///
200    /// # Errors
201    /// [`Error`] if the COM call fails or returns an unexpected type.
202    pub fn step_index(&self) -> Result<i32, Error> {
203        Ok(self.dispatch.get(sequence_context::STEP_INDEX)?.as_i32()?)
204    }
205
206    /// The index the engine will execute next (`SequenceContext.NextStepIndex`).
207    ///
208    /// # Errors
209    /// [`Error`] if the COM call fails or returns an unexpected type.
210    pub fn next_step_index(&self) -> Result<i32, Error> {
211        Ok(self
212            .dispatch
213            .get(sequence_context::NEXT_STEP_INDEX)?
214            .as_i32()?)
215    }
216
217    /// The index executed immediately before this point (`SequenceContext.PreviousStepIndex`).
218    ///
219    /// # Errors
220    /// [`Error`] if the COM call fails or returns an unexpected type.
221    pub fn previous_step_index(&self) -> Result<i32, Error> {
222        Ok(self
223            .dispatch
224            .get(sequence_context::PREVIOUS_STEP_INDEX)?
225            .as_i32()?)
226    }
227
228    /// Changes the next step index (`SequenceContext.NextStepIndex`).
229    ///
230    /// # Errors
231    /// [`Error`] if the COM call fails.
232    pub fn set_next_step_index(&self, index: i32) -> Result<(), Error> {
233        self.dispatch.put(
234            sequence_context::NEXT_STEP_INDEX,
235            rs_teststand_sys::Value::I32(index),
236        )?;
237        Ok(())
238    }
239
240    /// Changes the previous-step index (`SequenceContext.PreviousStepIndex`).
241    ///
242    /// # Errors
243    /// [`Error`] if the COM call fails.
244    pub fn set_previous_step_index(&self, index: i32) -> Result<(), Error> {
245        self.dispatch.put(
246            sequence_context::PREVIOUS_STEP_INDEX,
247            rs_teststand_sys::Value::I32(index),
248        )?;
249        Ok(())
250    }
251
252    /// The loop position at this point (`SequenceContext.LoopIndex`).
253    ///
254    /// This view is a 32-bit integer. Read `RunState.LoopIndex` as a number
255    /// through [`as_property_object`](Self::as_property_object) when the
256    /// fractional or wider value matters.
257    ///
258    /// # Errors
259    /// [`Error`] if the COM call fails or returns an unexpected type.
260    pub fn loop_index(&self) -> Result<i32, Error> {
261        Ok(self.dispatch.get(sequence_context::LOOP_INDEX)?.as_i32()?)
262    }
263
264    /// Changes the loop position (`SequenceContext.LoopIndex`).
265    ///
266    /// # Errors
267    /// [`Error`] if the COM call fails.
268    pub fn set_loop_index(&self, index: i32) -> Result<(), Error> {
269        self.dispatch.put(
270            sequence_context::LOOP_INDEX,
271            rs_teststand_sys::Value::I32(index),
272        )?;
273        Ok(())
274    }
275
276    /// The next step, when one exists (`SequenceContext.NextStep`).
277    ///
278    /// # Errors
279    /// [`Error`] if the sequence is at its end or the COM call fails.
280    pub fn next_step(&self) -> Result<crate::Step, Error> {
281        Ok(crate::Step::new(
282            self.dispatch
283                .get(sequence_context::NEXT_STEP)?
284                .into_object()?,
285        ))
286    }
287
288    /// The preceding step, when one exists (`SequenceContext.PreviousStep`).
289    ///
290    /// # Errors
291    /// [`Error`] if the sequence is at its start or the COM call fails.
292    pub fn previous_step(&self) -> Result<crate::Step, Error> {
293        Ok(crate::Step::new(
294            self.dispatch
295                .get(sequence_context::PREVIOUS_STEP)?
296                .into_object()?,
297        ))
298    }
299
300    /// How many steps have run so far (`SequenceContext.NumStepsExecuted`).
301    ///
302    /// # Errors
303    /// [`Error`] if the COM call fails or returns an unexpected type.
304    pub fn num_steps_executed(&self) -> Result<i32, Error> {
305        Ok(self
306            .dispatch
307            .get(sequence_context::NUM_STEPS_EXECUTED)?
308            .as_i32()?)
309    }
310
311    /// Whether the sequence has failed (`SequenceContext.SequenceFailed`).
312    ///
313    /// # Errors
314    /// [`Error`] if the COM call fails or returns an unexpected type.
315    pub fn sequence_failed(&self) -> Result<bool, Error> {
316        Ok(self
317            .dispatch
318            .get(sequence_context::SEQUENCE_FAILED)?
319            .as_bool()?)
320    }
321
322    /// Whether a run-time error has been reported
323    /// (`SequenceContext.ErrorReported`).
324    ///
325    /// # Errors
326    /// [`Error`] if the COM call fails or returns an unexpected type.
327    pub fn error_reported(&self) -> Result<bool, Error> {
328        Ok(self
329            .dispatch
330            .get(sequence_context::ERROR_REPORTED)?
331            .as_bool()?)
332    }
333
334    /// The current run-time error message
335    /// (`SequenceContext.RunTimeErrorMessage`).
336    ///
337    /// # Errors
338    /// [`Error`] if the COM call fails or returns an unexpected type.
339    pub fn run_time_error_message(&self) -> Result<String, Error> {
340        Ok(self
341            .dispatch
342            .get(sequence_context::RUN_TIME_ERROR_MESSAGE)?
343            .into_string()?)
344    }
345
346    /// The step being run right now (`SequenceContext.Step`).
347    ///
348    /// **Fallible by nature.** The engine documents this property as existing
349    /// only while a step executes, between steps, and at a breakpoint, there
350    /// is no current step. An error here is a legitimate state, not a defect,
351    /// which is why it is not reported as an empty value.
352    ///
353    /// # Errors
354    /// [`Error`] if no step is executing, or the COM call fails.
355    pub fn step(&self) -> Result<crate::Step, Error> {
356        Ok(crate::Step::new(
357            self.dispatch.get(sequence_context::STEP)?.into_object()?,
358        ))
359    }
360}