rs_teststand/property/options.rs
1//! Option flags accepted by `PropertyObject` lookups and mutations.
2//!
3//! # Why these are `bitflags` types
4//!
5//! The engine does not have a flags type. It takes a plain `Long` and documents
6//! the named constants that go into it, telling callers to combine them with the
7//! bitwise-OR operator. Passed through to Rust unchanged, that would mean `i32`
8//! parameters everywhere and call sites reading `set_val_string(path, 1, text)`,
9//! where `1` is meaningful only to someone holding the constant table.
10//!
11//! The [`bitflags`](https://docs.rs/bitflags) crate is this project'''s choice, not
12//! a mirror of anything in the COM API. It keeps the engine'''s own numbering
13//! exactly, so the value crossing the boundary is identical, while giving the
14//! Rust side named constants, combination with `|`, membership tests with
15//! `contains`, and a type that cannot be confused with an unrelated mask.
16//! `from_bits_retain` is used deliberately so a bit a newer engine sets, which
17//! this build has no name for, survives a read-modify-write instead of being
18//! silently dropped.
19
20bitflags::bitflags! {
21 /// Options controlling how a property lookup or mutation behaves
22 /// (`PropOption_*`).
23 ///
24 /// A bitmask, not an enumeration: the engine combines flags. Passing
25 /// [`PropertyOptions::NONE`] requests the default behavior.
26 ///
27 /// Several names share a bit because the engine reuses it per context (for
28 /// example [`Self::INSERT_IF_MISSING`] and [`Self::INSERT_ELEMENT`]); the
29 /// name documents intent at the call site.
30 ///
31 /// ```
32 /// use rs_teststand::PropertyOptions;
33 ///
34 /// let options = PropertyOptions::INSERT_IF_MISSING | PropertyOptions::COERCE_TO_STRING;
35 /// assert!(options.contains(PropertyOptions::INSERT_IF_MISSING));
36 /// assert!(PropertyOptions::NONE.is_empty());
37 /// ```
38 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
39 pub struct PropertyOptions: i32 {
40 /// Default behavior (`PropOption_NoOptions`).
41 const NONE = 0;
42 /// Create the property if it does not exist (`PropOption_InsertIfMissing`).
43 const INSERT_IF_MISSING = 1;
44 /// Insert an array element (`PropOption_InsertElement`).
45 const INSERT_ELEMENT = 1;
46 /// Delete the property if it exists (`PropOption_DeleteIfExists`).
47 const DELETE_IF_EXISTS = 2;
48 /// Remove an array element (`PropOption_RemoveElement`).
49 const REMOVE_ELEMENT = 2;
50 /// Leave an existing property untouched (`PropOption_DoNothingIfExists`).
51 const DO_NOTHING_IF_EXISTS = 4;
52 /// Set only when the property does not already exist
53 /// (`PropOption_SetOnlyIfDoesNotExist`).
54 const SET_ONLY_IF_DOES_NOT_EXIST = 5;
55 /// Convert from a number (`PropOption_CoerceFromNumber`).
56 const COERCE_FROM_NUMBER = 8;
57 /// Convert from a string (`PropOption_CoerceFromString`).
58 const COERCE_FROM_STRING = 16;
59 /// Convert to an enum (`PropOption_CoerceToEnum`).
60 const COERCE_TO_ENUM = 24;
61 /// Convert from a boolean (`PropOption_CoerceFromBoolean`).
62 const COERCE_FROM_BOOLEAN = 32;
63 /// Convert to a number (`PropOption_CoerceToNumber`).
64 const COERCE_TO_NUMBER = 64;
65 /// Convert to a string (`PropOption_CoerceToString`).
66 const COERCE_TO_STRING = 128;
67 /// Convert from an enum (`PropOption_CoerceFromEnum`).
68 const COERCE_FROM_ENUM = 192;
69 /// Convert to a boolean (`PropOption_CoerceToBoolean`).
70 const COERCE_TO_BOOLEAN = 256;
71 /// Do not take ownership (`PropOption_NotOwning`).
72 const NOT_OWNING = 512;
73 /// Resolve to the alias rather than its target (`PropOption_ReferToAlias`).
74 const REFER_TO_ALIAS = 1024;
75 /// Keep the existing name when copying (`PropOption_DoNotAdoptCurrentName`).
76 const DO_NOT_ADOPT_CURRENT_NAME = 2048;
77 /// Match names case-insensitively (`PropOption_CaseInsensitive`).
78 const CASE_INSENSITIVE = 4096;
79 /// Require an identical structure (`PropOption_RequireIdenticalStructure`).
80 const REQUIRE_IDENTICAL_STRUCTURE = 8192;
81 /// Do not recurse into subproperties (`PropOption_DoNotRecurse`).
82 const DO_NOT_RECURSE = 16384;
83 /// Convert from a reference (`PropOption_CoerceFromReference`).
84 const COERCE_FROM_REFERENCE = 65536;
85 /// Convert to a reference (`PropOption_CoerceToReference`).
86 const COERCE_TO_REFERENCE = 131_072;
87 /// Every conversion flag (`PropOption_Coerce`).
88 const COERCE = 197_112;
89 /// Treat unusable numbers as zero (`PropOption_CoerceBadNumbersToZero`).
90 const COERCE_BAD_NUMBERS_TO_ZERO = 262_144;
91 /// Allow deleting an otherwise protected property
92 /// (`PropOption_OverrideNotDeletable`).
93 const OVERRIDE_NOT_DELETABLE = 4_194_304;
94 /// Copy rather than share subproperties (`PropOption_DoNotShareProperties`).
95 const DO_NOT_SHARE_PROPERTIES = 134_217_728;
96 /// Copy every flag (`PropOption_CopyAllFlags`).
97 const COPY_ALL_FLAGS = 536_870_912;
98 }
99}
100
101bitflags::bitflags! {
102 /// Options for retrieving the templates file (`GetTemplatesFileOption_*`).
103 ///
104 /// The templates file is where the editor keeps the reusable variable, step
105 /// and sequence prototypes offered by its Insert menus. It is a station
106 /// file like any other, so a caller has to say whether a request should
107 /// load it when it is not already in memory.
108 ///
109 /// ```
110 /// use rs_teststand::GetTemplatesFileOptions;
111 ///
112 /// assert!(GetTemplatesFileOptions::NONE.is_empty());
113 /// assert_eq!(GetTemplatesFileOptions::LOAD_IF_NOT_LOADED.bits(), 1);
114 /// ```
115 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
116 pub struct GetTemplatesFileOptions: i32 {
117 /// Return the file only if it is already loaded
118 /// (`GetTemplatesFileOption_NoOptions`).
119 const NONE = 0;
120 /// Load the file when it is not in memory yet
121 /// (`GetTemplatesFileOption_LoadIfNotLoaded`).
122 const LOAD_IF_NOT_LOADED = 1;
123 }
124}
125
126#[cfg(test)]
127mod tests {
128 use super::{GetTemplatesFileOptions, PropertyOptions};
129
130 #[test]
131 fn none_is_empty_and_combines_to_nothing() {
132 assert!(PropertyOptions::NONE.is_empty());
133 assert_eq!(PropertyOptions::NONE.bits(), 0);
134 }
135
136 #[test]
137 fn flags_combine_and_test_membership() {
138 let options = PropertyOptions::INSERT_IF_MISSING | PropertyOptions::COERCE_TO_STRING;
139 assert!(options.contains(PropertyOptions::INSERT_IF_MISSING));
140 assert!(options.contains(PropertyOptions::COERCE_TO_STRING));
141 assert!(!options.contains(PropertyOptions::DO_NOT_RECURSE));
142 assert_eq!(options.bits(), 1 | 128);
143 }
144
145 #[test]
146 fn round_trips_through_raw_bits() {
147 let raw = PropertyOptions::COERCE.bits();
148 assert_eq!(
149 PropertyOptions::from_bits_retain(raw),
150 PropertyOptions::COERCE
151 );
152 }
153
154 #[test]
155 fn asking_for_the_templates_file_defaults_to_not_loading_it() {
156 // The default has to be the passive one: a host that only wants to know
157 // whether templates are in memory must not cause a file load by asking.
158 assert_eq!(GetTemplatesFileOptions::default().bits(), 0);
159 assert!(
160 !GetTemplatesFileOptions::default()
161 .contains(GetTemplatesFileOptions::LOAD_IF_NOT_LOADED)
162 );
163 }
164
165 #[test]
166 fn unknown_bits_from_the_engine_are_preserved() {
167 // A newer engine may set a flag this build does not name; a mask that
168 // round-trips through the wrapper must not silently drop it.
169 let unknown = 1 << 30;
170 assert_eq!(PropertyOptions::from_bits_retain(unknown).bits(), unknown);
171 }
172}