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}