Skip to main content

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}