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}