Skip to main content

scs_sdk/
value.rs

1use core::ffi::CStr;
2use core::fmt;
3
4use crate::{TelemetryApiVersion, sys};
5
6mod sealed {
7    pub trait Sealed {}
8}
9
10/// A three-dimensional single-precision vector reported by the SDK.
11///
12/// In vehicle-local space, positive X points right, positive Y points up, and
13/// positive Z points backwards. In world space, positive X points east,
14/// positive Y points up, and positive Z points south.
15#[derive(Clone, Copy, Debug, Default, PartialEq)]
16pub struct FVector {
17    pub x: f32,
18    pub y: f32,
19    pub z: f32,
20}
21
22/// A three-dimensional double-precision vector reported by the SDK.
23///
24/// World positions use double precision because ETS2 and ATS maps are large
25/// enough for single-precision coordinates to lose visible accuracy.
26#[derive(Clone, Copy, Debug, Default, PartialEq)]
27pub struct DVector {
28    pub x: f64,
29    pub y: f64,
30    pub z: f64,
31}
32
33/// Normalized object orientation used by the SCS SDK.
34///
35/// `heading` uses `[0.0, 1.0)` for `[0, 360)` degrees and increases
36/// counter-clockwise when viewed from above. `pitch` normally uses
37/// `[-0.25, 0.25]` for `[-90, 90]` degrees. `roll` normally uses
38/// `[-0.5, 0.5]` for `[-180, 180]` degrees.
39#[derive(Clone, Copy, Debug, Default, PartialEq)]
40pub struct Euler {
41    pub heading: f32,
42    pub pitch: f32,
43    pub roll: f32,
44}
45
46/// Single-precision position and orientation pair.
47#[derive(Clone, Copy, Debug, Default, PartialEq)]
48pub struct FPlacement {
49    pub position: FVector,
50    pub orientation: Euler,
51}
52
53/// Double-precision position with single-precision normalized orientation.
54///
55/// Unlike the ABI structure, this high-level type has no explicit alignment
56/// padding. It is safe to copy, compare, serialize, and retain after the SDK
57/// callback returns.
58#[derive(Clone, Copy, Debug, Default, PartialEq)]
59pub struct DPlacement {
60    pub position: DVector,
61    pub orientation: Euler,
62}
63
64/// Marker used by typed descriptors whose decoded value is a borrowed C
65/// string. SCS strings are UTF-8 and remain valid only for the current callback.
66#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
67pub struct StringValue;
68
69/// Error returned when a string is not in one documented SDK value catalog.
70///
71/// The original text remains owned or borrowed by the caller, so this compact
72/// error carries no allocation. Callers which need forward-compatible logging
73/// can report their input unchanged after a failed `FromStr` conversion.
74#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
75pub struct UnknownStringValue;
76
77impl fmt::Display for UnknownStringValue {
78    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
79        formatter.write_str("unknown SDK string value")
80    }
81}
82
83impl From<sys::ScsFVector> for FVector {
84    fn from(value: sys::ScsFVector) -> Self {
85        Self {
86            x: value.x,
87            y: value.y,
88            z: value.z,
89        }
90    }
91}
92
93impl From<sys::ScsDVector> for DVector {
94    fn from(value: sys::ScsDVector) -> Self {
95        Self {
96            x: value.x,
97            y: value.y,
98            z: value.z,
99        }
100    }
101}
102
103impl From<sys::ScsEuler> for Euler {
104    fn from(value: sys::ScsEuler) -> Self {
105        Self {
106            heading: value.heading,
107            pitch: value.pitch,
108            roll: value.roll,
109        }
110    }
111}
112
113impl From<sys::ScsFPlacement> for FPlacement {
114    fn from(value: sys::ScsFPlacement) -> Self {
115        Self {
116            position: value.position.into(),
117            orientation: value.orientation.into(),
118        }
119    }
120}
121
122impl From<sys::ScsDPlacement> for DPlacement {
123    fn from(value: sys::ScsDPlacement) -> Self {
124        Self {
125            position: value.position.into(),
126            orientation: value.orientation.into(),
127        }
128    }
129}
130
131#[derive(Clone, Copy, Debug, PartialEq, Eq)]
132#[repr(u32)]
133pub enum ValueType {
134    Bool = sys::SCS_VALUE_TYPE_BOOL,
135    I32 = sys::SCS_VALUE_TYPE_S32,
136    U32 = sys::SCS_VALUE_TYPE_U32,
137    U64 = sys::SCS_VALUE_TYPE_U64,
138    F32 = sys::SCS_VALUE_TYPE_FLOAT,
139    F64 = sys::SCS_VALUE_TYPE_DOUBLE,
140    FVector = sys::SCS_VALUE_TYPE_FVECTOR,
141    DVector = sys::SCS_VALUE_TYPE_DVECTOR,
142    Euler = sys::SCS_VALUE_TYPE_EULER,
143    FPlacement = sys::SCS_VALUE_TYPE_FPLACEMENT,
144    DPlacement = sys::SCS_VALUE_TYPE_DPLACEMENT,
145    String = sys::SCS_VALUE_TYPE_STRING,
146    I64 = sys::SCS_VALUE_TYPE_S64,
147}
148
149impl ValueType {
150    #[must_use]
151    pub const fn from_raw(value: sys::ScsValueType) -> Option<Self> {
152        match value {
153            sys::SCS_VALUE_TYPE_BOOL => Some(Self::Bool),
154            sys::SCS_VALUE_TYPE_S32 => Some(Self::I32),
155            sys::SCS_VALUE_TYPE_U32 => Some(Self::U32),
156            sys::SCS_VALUE_TYPE_U64 => Some(Self::U64),
157            sys::SCS_VALUE_TYPE_FLOAT => Some(Self::F32),
158            sys::SCS_VALUE_TYPE_DOUBLE => Some(Self::F64),
159            sys::SCS_VALUE_TYPE_FVECTOR => Some(Self::FVector),
160            sys::SCS_VALUE_TYPE_DVECTOR => Some(Self::DVector),
161            sys::SCS_VALUE_TYPE_EULER => Some(Self::Euler),
162            sys::SCS_VALUE_TYPE_FPLACEMENT => Some(Self::FPlacement),
163            sys::SCS_VALUE_TYPE_DPLACEMENT => Some(Self::DPlacement),
164            sys::SCS_VALUE_TYPE_STRING => Some(Self::String),
165            sys::SCS_VALUE_TYPE_S64 => Some(Self::I64),
166            _ => None,
167        }
168    }
169
170    /// Returns the numeric discriminator used by the C ABI.
171    ///
172    /// This is primarily useful to framework crates which must retain an SDK
173    /// value type after erasing the Rust marker type from a descriptor. Normal
174    /// plugin code should prefer the typed decoding methods on [`crate::Channel`]
175    /// and [`crate::Attribute`].
176    #[must_use]
177    pub const fn raw(self) -> sys::ScsValueType {
178        self as sys::ScsValueType
179    }
180
181    /// Oldest Telemetry API which defines this tagged-union representation.
182    ///
183    /// The Telemetry API 1.01 changelog explicitly adds signed 64-bit values.
184    /// Every other value tag exposed by SDK 1.14 belongs to API 1.00. This is
185    /// representation availability, not a promise that every channel supports
186    /// every representation: SCS still performs the channel-specific
187    /// conversion check during registration.
188    #[must_use]
189    pub const fn minimum_api_version(self) -> TelemetryApiVersion {
190        match self {
191            Self::I64 => TelemetryApiVersion::V1_01,
192            Self::Bool
193            | Self::I32
194            | Self::U32
195            | Self::U64
196            | Self::F32
197            | Self::F64
198            | Self::FVector
199            | Self::DVector
200            | Self::Euler
201            | Self::FPlacement
202            | Self::DPlacement
203            | Self::String => TelemetryApiVersion::V1_00,
204        }
205    }
206}
207
208/// Associates a Rust type with one SCS tagged-union member and its safe
209/// callback-time decoded representation.
210///
211/// Primitive and geometry values decode by value. [`StringValue`] decodes to a
212/// borrowed [`CStr`] whose lifetime cannot outlive the SDK callback.
213pub trait SdkValue: sealed::Sealed {
214    type Decoded<'a>;
215
216    /// High-level discriminator retained by type-erased framework descriptors.
217    const VALUE_TYPE: ValueType;
218
219    /// Numeric discriminator passed across the C ABI.
220    const TYPE: sys::ScsValueType;
221
222    fn decode(value: ValueRef<'_>) -> Option<Self::Decoded<'_>>;
223}
224
225macro_rules! scalar_sdk_value {
226    ($type:ty, $value_type:expr, $tag:expr, $getter:ident) => {
227        impl sealed::Sealed for $type {}
228
229        impl SdkValue for $type {
230            type Decoded<'a> = $type;
231
232            const VALUE_TYPE: ValueType = $value_type;
233            const TYPE: sys::ScsValueType = $tag;
234
235            fn decode(value: ValueRef<'_>) -> Option<Self::Decoded<'_>> {
236                value.$getter()
237            }
238        }
239    };
240}
241
242scalar_sdk_value!(bool, ValueType::Bool, sys::SCS_VALUE_TYPE_BOOL, as_bool);
243scalar_sdk_value!(i32, ValueType::I32, sys::SCS_VALUE_TYPE_S32, as_i32);
244scalar_sdk_value!(u32, ValueType::U32, sys::SCS_VALUE_TYPE_U32, as_u32);
245scalar_sdk_value!(u64, ValueType::U64, sys::SCS_VALUE_TYPE_U64, as_u64);
246scalar_sdk_value!(i64, ValueType::I64, sys::SCS_VALUE_TYPE_S64, as_i64);
247scalar_sdk_value!(f32, ValueType::F32, sys::SCS_VALUE_TYPE_FLOAT, as_f32);
248scalar_sdk_value!(f64, ValueType::F64, sys::SCS_VALUE_TYPE_DOUBLE, as_f64);
249scalar_sdk_value!(
250    FVector,
251    ValueType::FVector,
252    sys::SCS_VALUE_TYPE_FVECTOR,
253    as_fvector
254);
255scalar_sdk_value!(
256    DVector,
257    ValueType::DVector,
258    sys::SCS_VALUE_TYPE_DVECTOR,
259    as_dvector
260);
261scalar_sdk_value!(Euler, ValueType::Euler, sys::SCS_VALUE_TYPE_EULER, as_euler);
262scalar_sdk_value!(
263    FPlacement,
264    ValueType::FPlacement,
265    sys::SCS_VALUE_TYPE_FPLACEMENT,
266    as_fplacement
267);
268scalar_sdk_value!(
269    DPlacement,
270    ValueType::DPlacement,
271    sys::SCS_VALUE_TYPE_DPLACEMENT,
272    as_dplacement
273);
274
275impl sealed::Sealed for StringValue {}
276
277impl SdkValue for StringValue {
278    type Decoded<'a> = &'a CStr;
279
280    const VALUE_TYPE: ValueType = ValueType::String;
281    const TYPE: sys::ScsValueType = sys::SCS_VALUE_TYPE_STRING;
282
283    fn decode(value: ValueRef<'_>) -> Option<Self::Decoded<'_>> {
284        value.as_c_str()
285    }
286}
287
288#[derive(Clone, Copy)]
289pub struct ValueRef<'a> {
290    raw: &'a sys::ScsValue,
291}
292
293impl<'a> ValueRef<'a> {
294    pub(crate) const fn from_ref(raw: &'a sys::ScsValue) -> Self {
295        Self { raw }
296    }
297
298    /// Borrows a tagged SDK value for the duration of its callback.
299    ///
300    /// # Safety
301    ///
302    /// `value` must either be null or point to a correctly aligned, initialized
303    /// [`sys::ScsValue`] that remains alive for `'a`. For a known type tag, the
304    /// corresponding union member must be initialized. Unknown tags are
305    /// preserved without reading the union. String members must be non-null,
306    /// NUL-terminated, and remain alive for the same lifetime.
307    #[must_use]
308    pub unsafe fn from_ptr(value: *const sys::ScsValue) -> Option<Self> {
309        // SAFETY: The caller guarantees that a non-null pointer is aligned,
310        // initialized as `ScsValue`, and valid for the returned lifetime.
311        // `as_ref` additionally maps a null pointer to `None`.
312        unsafe { value.as_ref() }.map(|raw| Self { raw })
313    }
314
315    #[must_use]
316    pub const fn raw_type(self) -> sys::ScsValueType {
317        self.raw.type_
318    }
319
320    #[must_use]
321    pub const fn value_type(self) -> Option<ValueType> {
322        ValueType::from_raw(self.raw.type_)
323    }
324
325    #[must_use]
326    pub fn as_bool(self) -> Option<bool> {
327        if self.value_type() == Some(ValueType::Bool) {
328            // SAFETY: The checked tag selects the initialized bool member.
329            Some(unsafe { self.raw.value.value_bool.value != 0 })
330        } else {
331            None
332        }
333    }
334
335    #[must_use]
336    pub fn as_i32(self) -> Option<i32> {
337        if self.value_type() == Some(ValueType::I32) {
338            // SAFETY: The checked tag selects the initialized signed-32 member.
339            Some(unsafe { self.raw.value.value_s32.value })
340        } else {
341            None
342        }
343    }
344
345    #[must_use]
346    pub fn as_u32(self) -> Option<u32> {
347        if self.value_type() == Some(ValueType::U32) {
348            // SAFETY: The checked tag selects the initialized unsigned-32 member.
349            Some(unsafe { self.raw.value.value_u32.value })
350        } else {
351            None
352        }
353    }
354
355    #[must_use]
356    pub fn as_u64(self) -> Option<u64> {
357        if self.value_type() == Some(ValueType::U64) {
358            // SAFETY: The checked tag selects the initialized unsigned-64 member.
359            Some(unsafe { self.raw.value.value_u64.value })
360        } else {
361            None
362        }
363    }
364
365    #[must_use]
366    pub fn as_i64(self) -> Option<i64> {
367        if self.value_type() == Some(ValueType::I64) {
368            // SAFETY: The checked tag selects the initialized signed-64 member.
369            Some(unsafe { self.raw.value.value_s64.value })
370        } else {
371            None
372        }
373    }
374
375    #[must_use]
376    pub fn as_f32(self) -> Option<f32> {
377        if self.value_type() == Some(ValueType::F32) {
378            // SAFETY: The checked tag selects the initialized float member.
379            Some(unsafe { self.raw.value.value_float.value })
380        } else {
381            None
382        }
383    }
384
385    #[must_use]
386    pub fn as_f64(self) -> Option<f64> {
387        if self.value_type() == Some(ValueType::F64) {
388            // SAFETY: The checked tag selects the initialized double member.
389            Some(unsafe { self.raw.value.value_double.value })
390        } else {
391            None
392        }
393    }
394
395    #[must_use]
396    pub fn as_fvector(self) -> Option<FVector> {
397        if self.value_type() == Some(ValueType::FVector) {
398            // SAFETY: The checked tag selects the initialized float-vector member.
399            Some(unsafe { self.raw.value.value_fvector }.into())
400        } else {
401            None
402        }
403    }
404
405    #[must_use]
406    pub fn as_dvector(self) -> Option<DVector> {
407        if self.value_type() == Some(ValueType::DVector) {
408            // SAFETY: The checked tag selects the initialized double-vector member.
409            Some(unsafe { self.raw.value.value_dvector }.into())
410        } else {
411            None
412        }
413    }
414
415    #[must_use]
416    pub fn as_euler(self) -> Option<Euler> {
417        if self.value_type() == Some(ValueType::Euler) {
418            // SAFETY: The checked tag selects the initialized Euler member.
419            Some(unsafe { self.raw.value.value_euler }.into())
420        } else {
421            None
422        }
423    }
424
425    #[must_use]
426    pub fn as_fplacement(self) -> Option<FPlacement> {
427        if self.value_type() == Some(ValueType::FPlacement) {
428            // SAFETY: The checked tag selects the initialized float-placement member.
429            Some(unsafe { self.raw.value.value_fplacement }.into())
430        } else {
431            None
432        }
433    }
434
435    #[must_use]
436    pub fn as_dplacement(self) -> Option<DPlacement> {
437        if self.value_type() == Some(ValueType::DPlacement) {
438            // SAFETY: The checked tag selects the initialized double-placement member.
439            Some(unsafe { self.raw.value.value_dplacement }.into())
440        } else {
441            None
442        }
443    }
444
445    #[must_use]
446    pub fn as_c_str(self) -> Option<&'a CStr> {
447        if self.value_type() != Some(ValueType::String) {
448            return None;
449        }
450
451        // SAFETY: The checked tag selects the initialized string member. Its
452        // pointer is validated separately before constructing a `CStr`.
453        let pointer = unsafe { self.raw.value.value_string.value };
454        if pointer.is_null() {
455            return None;
456        }
457
458        // SAFETY: String values are guaranteed to be non-null, NUL-terminated,
459        // and valid for the duration of the current SDK callback.
460        Some(unsafe { CStr::from_ptr(pointer) })
461    }
462}
463
464#[cfg(test)]
465mod tests {
466    use super::*;
467
468    fn value_ref(raw: &sys::ScsValue) -> ValueRef<'_> {
469        // SAFETY: A shared Rust reference proves alignment, initialization of
470        // the outer `ScsValue`, and validity for the returned borrow. Each test
471        // fixture initializes the union member selected by its known tag; an
472        // unknown tag is tested only through operations that do not read it.
473        unsafe { ValueRef::from_ptr(raw) }.expect("value reference should be present")
474    }
475
476    #[test]
477    fn signed_64_bit_values_are_the_only_v101_representation() {
478        for value_type in [
479            ValueType::Bool,
480            ValueType::I32,
481            ValueType::U32,
482            ValueType::U64,
483            ValueType::F32,
484            ValueType::F64,
485            ValueType::FVector,
486            ValueType::DVector,
487            ValueType::Euler,
488            ValueType::FPlacement,
489            ValueType::DPlacement,
490            ValueType::String,
491        ] {
492            assert_eq!(value_type.minimum_api_version(), TelemetryApiVersion::V1_00);
493        }
494        assert_eq!(
495            ValueType::I64.minimum_api_version(),
496            TelemetryApiVersion::V1_01
497        );
498    }
499
500    #[test]
501    fn decodes_only_the_tagged_union_member() {
502        let raw = sys::ScsValue {
503            type_: sys::SCS_VALUE_TYPE_FLOAT,
504            padding: sys::ScsPadding::new(0),
505            value: sys::ScsValueData {
506                value_float: sys::ScsValueFloat { value: 12.5 },
507            },
508        };
509        let value = value_ref(&raw);
510
511        assert_eq!(value.as_f32(), Some(12.5));
512        assert_eq!(value.as_i32(), None);
513        assert_eq!(value.value_type(), Some(ValueType::F32));
514    }
515
516    #[test]
517    fn preserves_unknown_value_tags() {
518        let raw = sys::ScsValue {
519            type_: 99,
520            padding: sys::ScsPadding::new(0),
521            value: sys::ScsValueData {
522                value_u64: sys::ScsValueU64 { value: 0 },
523            },
524        };
525        let value = value_ref(&raw);
526
527        assert_eq!(value.raw_type(), 99);
528        assert_eq!(value.value_type(), None);
529    }
530
531    #[test]
532    fn mismatched_getters_do_not_decode_an_inactive_union_member() {
533        let raw = sys::ScsValue {
534            type_: sys::SCS_VALUE_TYPE_BOOL,
535            padding: sys::ScsPadding::uninit(),
536            value: sys::ScsValueData {
537                value_bool: sys::ScsValueBool { value: 1 },
538            },
539        };
540        let value = value_ref(&raw);
541
542        assert_eq!(value.as_bool(), Some(true));
543        assert_eq!(value.as_i32(), None);
544        assert_eq!(value.as_u32(), None);
545        assert_eq!(value.as_i64(), None);
546        assert_eq!(value.as_u64(), None);
547        assert_eq!(value.as_f32(), None);
548        assert_eq!(value.as_f64(), None);
549        assert!(value.as_dplacement().is_none());
550        assert_eq!(value.as_c_str(), None);
551    }
552
553    #[test]
554    fn decodes_every_scalar_sdk_value_type() {
555        let signed_32 = sys::ScsValue {
556            type_: sys::SCS_VALUE_TYPE_S32,
557            padding: sys::ScsPadding::uninit(),
558            value: sys::ScsValueData {
559                value_s32: sys::ScsValueS32 { value: -32 },
560            },
561        };
562        let unsigned_32 = sys::ScsValue {
563            type_: sys::SCS_VALUE_TYPE_U32,
564            padding: sys::ScsPadding::uninit(),
565            value: sys::ScsValueData {
566                value_u32: sys::ScsValueU32 { value: 32 },
567            },
568        };
569        let signed_64 = sys::ScsValue {
570            type_: sys::SCS_VALUE_TYPE_S64,
571            padding: sys::ScsPadding::uninit(),
572            value: sys::ScsValueData {
573                value_s64: sys::ScsValueS64 { value: -64 },
574            },
575        };
576        let unsigned_64 = sys::ScsValue {
577            type_: sys::SCS_VALUE_TYPE_U64,
578            padding: sys::ScsPadding::uninit(),
579            value: sys::ScsValueData {
580                value_u64: sys::ScsValueU64 { value: 64 },
581            },
582        };
583        let double = sys::ScsValue {
584            type_: sys::SCS_VALUE_TYPE_DOUBLE,
585            padding: sys::ScsPadding::uninit(),
586            value: sys::ScsValueData {
587                value_double: sys::ScsValueDouble { value: 12.25 },
588            },
589        };
590        let string = sys::ScsValue {
591            type_: sys::SCS_VALUE_TYPE_STRING,
592            padding: sys::ScsPadding::uninit(),
593            value: sys::ScsValueData {
594                value_string: sys::ScsValueString {
595                    value: c"telemetry".as_ptr(),
596                },
597            },
598        };
599
600        let signed_32 = value_ref(&signed_32);
601        let unsigned_32 = value_ref(&unsigned_32);
602        let signed_64 = value_ref(&signed_64);
603        let unsigned_64 = value_ref(&unsigned_64);
604        let double = value_ref(&double);
605        let string = value_ref(&string);
606
607        assert_eq!(signed_32.as_i32(), Some(-32));
608        assert_eq!(unsigned_32.as_u32(), Some(32));
609        assert_eq!(signed_64.as_i64(), Some(-64));
610        assert_eq!(unsigned_64.as_u64(), Some(64));
611        assert_eq!(double.as_f64(), Some(12.25));
612        assert_eq!(string.as_c_str(), Some(c"telemetry"));
613    }
614
615    #[test]
616    fn decodes_vector_and_euler_geometry_types() {
617        let fvector = sys::ScsValue {
618            type_: sys::SCS_VALUE_TYPE_FVECTOR,
619            padding: sys::ScsPadding::uninit(),
620            value: sys::ScsValueData {
621                value_fvector: sys::ScsFVector {
622                    x: 1.0,
623                    y: 2.0,
624                    z: 3.0,
625                },
626            },
627        };
628        let dvector = sys::ScsValue {
629            type_: sys::SCS_VALUE_TYPE_DVECTOR,
630            padding: sys::ScsPadding::uninit(),
631            value: sys::ScsValueData {
632                value_dvector: sys::ScsDVector {
633                    x: 4.0,
634                    y: 5.0,
635                    z: 6.0,
636                },
637            },
638        };
639        let euler = sys::ScsEuler {
640            heading: 0.25,
641            pitch: -0.125,
642            roll: 0.5,
643        };
644        let raw_euler = sys::ScsValue {
645            type_: sys::SCS_VALUE_TYPE_EULER,
646            padding: sys::ScsPadding::uninit(),
647            value: sys::ScsValueData { value_euler: euler },
648        };
649        let fvector = value_ref(&fvector);
650        let dvector = value_ref(&dvector);
651        let raw_euler = value_ref(&raw_euler);
652
653        assert_eq!(
654            fvector.as_fvector(),
655            Some(FVector {
656                x: 1.0,
657                y: 2.0,
658                z: 3.0,
659            })
660        );
661        assert_eq!(
662            dvector.as_dvector(),
663            Some(DVector {
664                x: 4.0,
665                y: 5.0,
666                z: 6.0,
667            })
668        );
669        assert_eq!(
670            raw_euler.as_euler(),
671            Some(Euler {
672                heading: 0.25,
673                pitch: -0.125,
674                roll: 0.5,
675            })
676        );
677    }
678
679    #[test]
680    fn decodes_placements_without_reading_uninitialized_abi_padding() {
681        let euler = sys::ScsEuler {
682            heading: 0.25,
683            pitch: -0.125,
684            roll: 0.5,
685        };
686        let fplacement = sys::ScsValue {
687            type_: sys::SCS_VALUE_TYPE_FPLACEMENT,
688            padding: sys::ScsPadding::uninit(),
689            value: sys::ScsValueData {
690                value_fplacement: sys::ScsFPlacement {
691                    position: sys::ScsFVector {
692                        x: 7.0,
693                        y: 8.0,
694                        z: 9.0,
695                    },
696                    orientation: euler,
697                },
698            },
699        };
700        let dplacement = sys::ScsValue {
701            type_: sys::SCS_VALUE_TYPE_DPLACEMENT,
702            padding: sys::ScsPadding::uninit(),
703            value: sys::ScsValueData {
704                value_dplacement: sys::ScsDPlacement {
705                    position: sys::ScsDVector {
706                        x: 10.0,
707                        y: 11.0,
708                        z: 12.0,
709                    },
710                    orientation: euler,
711                    padding: sys::ScsPadding::uninit(),
712                },
713            },
714        };
715
716        let fplacement = value_ref(&fplacement);
717        let dplacement = value_ref(&dplacement);
718        assert_eq!(
719            fplacement.as_fplacement(),
720            Some(FPlacement {
721                position: FVector {
722                    x: 7.0,
723                    y: 8.0,
724                    z: 9.0,
725                },
726                orientation: Euler {
727                    heading: 0.25,
728                    pitch: -0.125,
729                    roll: 0.5,
730                },
731            })
732        );
733        assert_eq!(
734            dplacement.as_dplacement(),
735            Some(DPlacement {
736                position: DVector {
737                    x: 10.0,
738                    y: 11.0,
739                    z: 12.0,
740                },
741                orientation: Euler {
742                    heading: 0.25,
743                    pitch: -0.125,
744                    roll: 0.5,
745                },
746            })
747        );
748    }
749}