rs_teststand/sequence/sequence.rs
1//! Safe wrapper for a sequence within a sequence file.
2
3use rs_teststand_sys::{Dispatch, Value};
4
5use crate::Error;
6use crate::dispids::sequence;
7use crate::property::PropertyObject;
8
9/// One sequence inside a [`SequenceFile`](crate::SequenceFile).
10///
11/// A sequence owns two of the four variable scopes:
12///
13/// * [`locals`](Self::locals), storage private to one call of this sequence.
14/// * [`parameters`](Self::parameters), values the caller supplies.
15///
16/// The other two live elsewhere: file globals on the sequence file, and station
17/// globals on the engine.
18#[derive(Debug)]
19pub struct Sequence {
20 dispatch: Box<dyn Dispatch>,
21}
22
23impl Sequence {
24 /// Wraps a dispatch handle returned by the engine.
25 pub(crate) fn new(dispatch: Box<dyn Dispatch>) -> Self {
26 Self { dispatch }
27 }
28
29 /// The sequence's name (`Sequence.Name`).
30 ///
31 /// # Errors
32 /// [`Error`] if the COM call fails or returns an unexpected type.
33 pub fn name(&self) -> Result<String, Error> {
34 Ok(self.dispatch.get(sequence::NAME)?.into_string()?)
35 }
36
37 /// Renames the sequence (`Sequence.Name`).
38 ///
39 /// # Errors
40 /// [`Error`] if the COM call fails.
41 pub fn set_name(&self, name: &str) -> Result<(), Error> {
42 self.dispatch
43 .put(sequence::NAME, Value::Str(name.to_owned()))?;
44 Ok(())
45 }
46
47 /// An owned handle to the same sequence, for passing it back.
48 pub(crate) fn duplicate_dispatch(&self) -> Option<Box<dyn Dispatch>> {
49 self.dispatch.duplicate()
50 }
51
52 /// How many steps a group holds (`Sequence.GetNumSteps`).
53 ///
54 /// # Errors
55 /// [`Error`] if the COM call fails or returns an unexpected type.
56 pub fn get_num_steps(&self, group: crate::StepGroup) -> Result<i32, Error> {
57 Ok(self
58 .dispatch
59 .call(sequence::GET_NUM_STEPS, &[Value::I32(group.bits())])?
60 .as_i32()?)
61 }
62
63 /// A step by position within a group (`Sequence.GetStep`).
64 ///
65 /// # Errors
66 /// [`Error`] if the index is out of range or the COM call fails.
67 pub fn get_step(&self, index: i32, group: crate::StepGroup) -> Result<crate::Step, Error> {
68 Ok(crate::Step::new(
69 self.dispatch
70 .call(
71 sequence::GET_STEP,
72 &[Value::I32(index), Value::I32(group.bits())],
73 )?
74 .into_object()?,
75 ))
76 }
77
78 /// Places a step in a group at a position (`Sequence.InsertStep`).
79 ///
80 /// # Errors
81 /// [`Error`] if the index is out of range, the step is not a live object,
82 /// or the COM call fails.
83 pub fn insert_step(
84 &self,
85 step: &crate::Step,
86 index: i32,
87 group: crate::StepGroup,
88 ) -> Result<(), Error> {
89 let handle = step.duplicate_dispatch().ok_or(Error::UnexpectedType {
90 expected: "a live step object",
91 actual: "a test fake with no COM identity",
92 })?;
93 self.dispatch.call(
94 sequence::INSERT_STEP,
95 &[
96 Value::Object(handle),
97 Value::I32(index),
98 Value::I32(group.bits()),
99 ],
100 )?;
101 Ok(())
102 }
103
104 /// Inserts a copy of a template step and returns the copy
105 /// (`PropertyObject.Clone` + `Sequence.InsertStep`).
106 ///
107 /// Composed rather than a COM member of its own, because the raw sequence
108 /// has a trap in it. `Clone` lives on `PropertyObject`, so a copied step
109 /// arrives on the `PropertyObject` interface, and the `Step` interface
110 /// shares none of its dispatch identifiers. Reading `Name` off such a copy
111 /// would dispatch a step identifier against a property-object interface and
112 /// take the process down rather than fail. Inserting first and reading the
113 /// step back from the sequence is the route that stays on the right
114 /// interface, so this returns the inserted step and never exposes the
115 /// intermediate.
116 ///
117 /// The template itself is untouched and can be inserted any number of
118 /// times. Each copy still carries the template's step ID until
119 /// [`Step::create_new_unique_step_id`](crate::Step::create_new_unique_step_id)
120 /// is called on it.
121 ///
122 /// # Errors
123 /// [`Error`] if the template is not a live object, the index is out of
124 /// range, or a COM call fails.
125 pub fn insert_step_from_template(
126 &self,
127 template: &PropertyObject,
128 index: i32,
129 group: crate::StepGroup,
130 ) -> Result<crate::Step, Error> {
131 let copy = template.clone_property("", crate::PropertyOptions::NONE.bits())?;
132 let handle = copy.duplicate_dispatch().ok_or(Error::UnexpectedType {
133 expected: "a live template object",
134 actual: "a test fake with no COM identity",
135 })?;
136 self.dispatch.call(
137 sequence::INSERT_STEP,
138 &[
139 Value::Object(handle),
140 Value::I32(index),
141 Value::I32(group.bits()),
142 ],
143 )?;
144 self.get_step(index, group)
145 }
146
147 /// The sequence as a property tree (`Sequence.AsPropertyObject`).
148 ///
149 /// This is also how a sequence becomes a template: a clone taken here is a
150 /// complete, detached copy of the sequence and its steps.
151 ///
152 /// # Errors
153 /// [`Error`] if the COM call fails or returns an unexpected type.
154 pub fn as_property_object(&self) -> Result<PropertyObject, Error> {
155 Ok(PropertyObject::new(
156 self.dispatch
157 .call(sequence::AS_PROPERTY_OBJECT, &[])?
158 .into_object()?,
159 ))
160 }
161
162 /// Re-identifies every step in the sequence
163 /// (`Sequence.CreateNewUniqueStepIds`).
164 ///
165 /// The bulk form of
166 /// [`Step::create_new_unique_step_id`](crate::Step::create_new_unique_step_id),
167 /// and what a sequence cloned from a template needs: every step in the copy
168 /// arrives holding the identity of its counterpart in the original.
169 ///
170 /// # Errors
171 /// [`Error`] if the COM call fails.
172 pub fn create_new_unique_step_ids(&self) -> Result<(), Error> {
173 self.dispatch
174 .call(sequence::CREATE_NEW_UNIQUE_STEP_IDS, &[])?;
175 Ok(())
176 }
177
178 /// The sequence's local variables (`Sequence.Locals`).
179 ///
180 /// Locals are per call: each invocation of the sequence gets its own copy,
181 /// so what this returns at edit time is the definition rather than any
182 /// running instance's values.
183 ///
184 /// # Errors
185 /// [`Error`] if the COM call fails or returns an unexpected type.
186 pub fn locals(&self) -> Result<PropertyObject, Error> {
187 Ok(PropertyObject::new(
188 self.dispatch.get(sequence::LOCALS)?.into_object()?,
189 ))
190 }
191
192 /// The sequence's parameters (`Sequence.Parameters`).
193 ///
194 /// # Errors
195 /// [`Error`] if the COM call fails or returns an unexpected type.
196 pub fn parameters(&self) -> Result<PropertyObject, Error> {
197 Ok(PropertyObject::new(
198 self.dispatch.get(sequence::PARAMETERS)?.into_object()?,
199 ))
200 }
201}
202
203#[cfg(test)]
204mod tests {
205 use std::collections::HashMap;
206
207 use rs_teststand_sys::{ComError, Dispatch, Value};
208
209 use super::Sequence;
210 use crate::Error;
211 use crate::dispids::sequence;
212
213 #[derive(Debug)]
214 struct Fake {
215 properties: HashMap<i32, Value>,
216 }
217
218 impl Dispatch for Fake {
219 fn get(&self, dispid: i32) -> Result<Value, ComError> {
220 match self.properties.get(&dispid) {
221 Some(Value::Str(text)) => Ok(Value::Str(text.clone())),
222 _ => Err(ComError::hresult(-17000, "fake: unscripted")),
223 }
224 }
225
226 fn put(&self, _dispid: i32, _value: Value) -> Result<(), ComError> {
227 Ok(())
228 }
229
230 fn call(&self, _dispid: i32, _args: &[Value]) -> Result<Value, ComError> {
231 Err(ComError::hresult(-17000, "fake: unscripted"))
232 }
233 }
234
235 #[test]
236 fn reads_its_name() -> Result<(), Error> {
237 let sequence = Sequence::new(Box::new(Fake {
238 properties: HashMap::from([(sequence::NAME, Value::Str("MainSequence".to_owned()))]),
239 }));
240 assert_eq!(sequence.name()?, "MainSequence");
241 Ok(())
242 }
243
244 #[test]
245 fn locals_and_parameters_are_distinct_dispids() {
246 // Swapping these two is a silent, plausible bug: both return a
247 // PropertyObject, so a mix-up would only show up as variables appearing
248 // in the wrong scope on a live engine.
249 assert_ne!(sequence::LOCALS, sequence::PARAMETERS);
250 assert_eq!(sequence::LOCALS, 0x33);
251 assert_eq!(sequence::PARAMETERS, 0x32);
252 }
253}