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`. Its type tag must identify
304    /// the initialized union member. String members must be non-null,
305    /// NUL-terminated, and remain alive for the same lifetime.
306    #[must_use]
307    pub unsafe fn from_ptr(value: *const sys::ScsValue) -> Option<Self> {
308        unsafe { value.as_ref() }.map(|raw| Self { raw })
309    }
310
311    #[must_use]
312    pub const fn raw_type(self) -> sys::ScsValueType {
313        self.raw.type_
314    }
315
316    #[must_use]
317    pub const fn value_type(self) -> Option<ValueType> {
318        ValueType::from_raw(self.raw.type_)
319    }
320
321    #[must_use]
322    pub fn as_bool(self) -> Option<bool> {
323        if self.value_type() == Some(ValueType::Bool) {
324            Some(unsafe { self.raw.value.value_bool.value != 0 })
325        } else {
326            None
327        }
328    }
329
330    #[must_use]
331    pub fn as_i32(self) -> Option<i32> {
332        if self.value_type() == Some(ValueType::I32) {
333            Some(unsafe { self.raw.value.value_s32.value })
334        } else {
335            None
336        }
337    }
338
339    #[must_use]
340    pub fn as_u32(self) -> Option<u32> {
341        if self.value_type() == Some(ValueType::U32) {
342            Some(unsafe { self.raw.value.value_u32.value })
343        } else {
344            None
345        }
346    }
347
348    #[must_use]
349    pub fn as_u64(self) -> Option<u64> {
350        if self.value_type() == Some(ValueType::U64) {
351            Some(unsafe { self.raw.value.value_u64.value })
352        } else {
353            None
354        }
355    }
356
357    #[must_use]
358    pub fn as_i64(self) -> Option<i64> {
359        if self.value_type() == Some(ValueType::I64) {
360            Some(unsafe { self.raw.value.value_s64.value })
361        } else {
362            None
363        }
364    }
365
366    #[must_use]
367    pub fn as_f32(self) -> Option<f32> {
368        if self.value_type() == Some(ValueType::F32) {
369            Some(unsafe { self.raw.value.value_float.value })
370        } else {
371            None
372        }
373    }
374
375    #[must_use]
376    pub fn as_f64(self) -> Option<f64> {
377        if self.value_type() == Some(ValueType::F64) {
378            Some(unsafe { self.raw.value.value_double.value })
379        } else {
380            None
381        }
382    }
383
384    #[must_use]
385    pub fn as_fvector(self) -> Option<FVector> {
386        if self.value_type() == Some(ValueType::FVector) {
387            Some(unsafe { self.raw.value.value_fvector }.into())
388        } else {
389            None
390        }
391    }
392
393    #[must_use]
394    pub fn as_dvector(self) -> Option<DVector> {
395        if self.value_type() == Some(ValueType::DVector) {
396            Some(unsafe { self.raw.value.value_dvector }.into())
397        } else {
398            None
399        }
400    }
401
402    #[must_use]
403    pub fn as_euler(self) -> Option<Euler> {
404        if self.value_type() == Some(ValueType::Euler) {
405            Some(unsafe { self.raw.value.value_euler }.into())
406        } else {
407            None
408        }
409    }
410
411    #[must_use]
412    pub fn as_fplacement(self) -> Option<FPlacement> {
413        if self.value_type() == Some(ValueType::FPlacement) {
414            Some(unsafe { self.raw.value.value_fplacement }.into())
415        } else {
416            None
417        }
418    }
419
420    #[must_use]
421    pub fn as_dplacement(self) -> Option<DPlacement> {
422        if self.value_type() == Some(ValueType::DPlacement) {
423            Some(unsafe { self.raw.value.value_dplacement }.into())
424        } else {
425            None
426        }
427    }
428
429    #[must_use]
430    pub fn as_c_str(self) -> Option<&'a CStr> {
431        if self.value_type() != Some(ValueType::String) {
432            return None;
433        }
434
435        let pointer = unsafe { self.raw.value.value_string.value };
436        if pointer.is_null() {
437            return None;
438        }
439
440        // SAFETY: String values are guaranteed to be non-null, NUL-terminated,
441        // and valid for the duration of the current SDK callback.
442        Some(unsafe { CStr::from_ptr(pointer) })
443    }
444}
445
446#[cfg(test)]
447mod tests {
448    use super::*;
449
450    #[test]
451    fn signed_64_bit_values_are_the_only_v101_representation() {
452        for value_type in [
453            ValueType::Bool,
454            ValueType::I32,
455            ValueType::U32,
456            ValueType::U64,
457            ValueType::F32,
458            ValueType::F64,
459            ValueType::FVector,
460            ValueType::DVector,
461            ValueType::Euler,
462            ValueType::FPlacement,
463            ValueType::DPlacement,
464            ValueType::String,
465        ] {
466            assert_eq!(value_type.minimum_api_version(), TelemetryApiVersion::V1_00);
467        }
468        assert_eq!(
469            ValueType::I64.minimum_api_version(),
470            TelemetryApiVersion::V1_01
471        );
472    }
473
474    #[test]
475    fn decodes_only_the_tagged_union_member() {
476        let raw = sys::ScsValue {
477            type_: sys::SCS_VALUE_TYPE_FLOAT,
478            padding: sys::ScsPadding::new(0),
479            value: sys::ScsValueData {
480                value_float: sys::ScsValueFloat { value: 12.5 },
481            },
482        };
483        let value = unsafe { ValueRef::from_ptr(&raw const raw) }.expect("value should be present");
484
485        assert_eq!(value.as_f32(), Some(12.5));
486        assert_eq!(value.as_i32(), None);
487        assert_eq!(value.value_type(), Some(ValueType::F32));
488    }
489
490    #[test]
491    fn preserves_unknown_value_tags() {
492        let raw = sys::ScsValue {
493            type_: 99,
494            padding: sys::ScsPadding::new(0),
495            value: sys::ScsValueData {
496                value_u64: sys::ScsValueU64 { value: 0 },
497            },
498        };
499        let value = unsafe { ValueRef::from_ptr(&raw const raw) }.expect("value should be present");
500
501        assert_eq!(value.raw_type(), 99);
502        assert_eq!(value.value_type(), None);
503    }
504
505    #[test]
506    fn mismatched_getters_do_not_decode_an_inactive_union_member() {
507        let raw = sys::ScsValue {
508            type_: sys::SCS_VALUE_TYPE_BOOL,
509            padding: sys::ScsPadding::uninit(),
510            value: sys::ScsValueData {
511                value_bool: sys::ScsValueBool { value: 1 },
512            },
513        };
514        let value = unsafe { ValueRef::from_ptr(&raw const raw) }.expect("value should be present");
515
516        assert_eq!(value.as_bool(), Some(true));
517        assert_eq!(value.as_i32(), None);
518        assert_eq!(value.as_u32(), None);
519        assert_eq!(value.as_i64(), None);
520        assert_eq!(value.as_u64(), None);
521        assert_eq!(value.as_f32(), None);
522        assert_eq!(value.as_f64(), None);
523        assert!(value.as_dplacement().is_none());
524        assert_eq!(value.as_c_str(), None);
525    }
526
527    #[test]
528    fn decodes_every_scalar_sdk_value_type() {
529        let signed_32 = sys::ScsValue {
530            type_: sys::SCS_VALUE_TYPE_S32,
531            padding: sys::ScsPadding::uninit(),
532            value: sys::ScsValueData {
533                value_s32: sys::ScsValueS32 { value: -32 },
534            },
535        };
536        let unsigned_32 = sys::ScsValue {
537            type_: sys::SCS_VALUE_TYPE_U32,
538            padding: sys::ScsPadding::uninit(),
539            value: sys::ScsValueData {
540                value_u32: sys::ScsValueU32 { value: 32 },
541            },
542        };
543        let signed_64 = sys::ScsValue {
544            type_: sys::SCS_VALUE_TYPE_S64,
545            padding: sys::ScsPadding::uninit(),
546            value: sys::ScsValueData {
547                value_s64: sys::ScsValueS64 { value: -64 },
548            },
549        };
550        let unsigned_64 = sys::ScsValue {
551            type_: sys::SCS_VALUE_TYPE_U64,
552            padding: sys::ScsPadding::uninit(),
553            value: sys::ScsValueData {
554                value_u64: sys::ScsValueU64 { value: 64 },
555            },
556        };
557        let double = sys::ScsValue {
558            type_: sys::SCS_VALUE_TYPE_DOUBLE,
559            padding: sys::ScsPadding::uninit(),
560            value: sys::ScsValueData {
561                value_double: sys::ScsValueDouble { value: 12.25 },
562            },
563        };
564        let string = sys::ScsValue {
565            type_: sys::SCS_VALUE_TYPE_STRING,
566            padding: sys::ScsPadding::uninit(),
567            value: sys::ScsValueData {
568                value_string: sys::ScsValueString {
569                    value: c"telemetry".as_ptr(),
570                },
571            },
572        };
573
574        let signed_32 = unsafe { ValueRef::from_ptr(&raw const signed_32) }
575            .expect("signed 32-bit value should be present");
576        let unsigned_32 = unsafe { ValueRef::from_ptr(&raw const unsigned_32) }
577            .expect("unsigned 32-bit value should be present");
578        let signed_64 = unsafe { ValueRef::from_ptr(&raw const signed_64) }
579            .expect("signed 64-bit value should be present");
580        let unsigned_64 = unsafe { ValueRef::from_ptr(&raw const unsigned_64) }
581            .expect("unsigned 64-bit value should be present");
582        let double = unsafe { ValueRef::from_ptr(&raw const double) }
583            .expect("double value should be present");
584        let string = unsafe { ValueRef::from_ptr(&raw const string) }
585            .expect("string value should be present");
586
587        assert_eq!(signed_32.as_i32(), Some(-32));
588        assert_eq!(unsigned_32.as_u32(), Some(32));
589        assert_eq!(signed_64.as_i64(), Some(-64));
590        assert_eq!(unsigned_64.as_u64(), Some(64));
591        assert_eq!(double.as_f64(), Some(12.25));
592        assert_eq!(string.as_c_str(), Some(c"telemetry"));
593    }
594
595    #[test]
596    fn decodes_vector_and_euler_geometry_types() {
597        let fvector = sys::ScsValue {
598            type_: sys::SCS_VALUE_TYPE_FVECTOR,
599            padding: sys::ScsPadding::uninit(),
600            value: sys::ScsValueData {
601                value_fvector: sys::ScsFVector {
602                    x: 1.0,
603                    y: 2.0,
604                    z: 3.0,
605                },
606            },
607        };
608        let dvector = sys::ScsValue {
609            type_: sys::SCS_VALUE_TYPE_DVECTOR,
610            padding: sys::ScsPadding::uninit(),
611            value: sys::ScsValueData {
612                value_dvector: sys::ScsDVector {
613                    x: 4.0,
614                    y: 5.0,
615                    z: 6.0,
616                },
617            },
618        };
619        let euler = sys::ScsEuler {
620            heading: 0.25,
621            pitch: -0.125,
622            roll: 0.5,
623        };
624        let raw_euler = sys::ScsValue {
625            type_: sys::SCS_VALUE_TYPE_EULER,
626            padding: sys::ScsPadding::uninit(),
627            value: sys::ScsValueData { value_euler: euler },
628        };
629        let fvector = unsafe { ValueRef::from_ptr(&raw const fvector) }
630            .expect("float vector should be present");
631        let dvector = unsafe { ValueRef::from_ptr(&raw const dvector) }
632            .expect("double vector should be present");
633        let raw_euler = unsafe { ValueRef::from_ptr(&raw const raw_euler) }
634            .expect("Euler value should be present");
635
636        assert_eq!(
637            fvector.as_fvector(),
638            Some(FVector {
639                x: 1.0,
640                y: 2.0,
641                z: 3.0,
642            })
643        );
644        assert_eq!(
645            dvector.as_dvector(),
646            Some(DVector {
647                x: 4.0,
648                y: 5.0,
649                z: 6.0,
650            })
651        );
652        assert_eq!(
653            raw_euler.as_euler(),
654            Some(Euler {
655                heading: 0.25,
656                pitch: -0.125,
657                roll: 0.5,
658            })
659        );
660    }
661
662    #[test]
663    fn decodes_placements_without_reading_uninitialized_abi_padding() {
664        let euler = sys::ScsEuler {
665            heading: 0.25,
666            pitch: -0.125,
667            roll: 0.5,
668        };
669        let fplacement = sys::ScsValue {
670            type_: sys::SCS_VALUE_TYPE_FPLACEMENT,
671            padding: sys::ScsPadding::uninit(),
672            value: sys::ScsValueData {
673                value_fplacement: sys::ScsFPlacement {
674                    position: sys::ScsFVector {
675                        x: 7.0,
676                        y: 8.0,
677                        z: 9.0,
678                    },
679                    orientation: euler,
680                },
681            },
682        };
683        let dplacement = sys::ScsValue {
684            type_: sys::SCS_VALUE_TYPE_DPLACEMENT,
685            padding: sys::ScsPadding::uninit(),
686            value: sys::ScsValueData {
687                value_dplacement: sys::ScsDPlacement {
688                    position: sys::ScsDVector {
689                        x: 10.0,
690                        y: 11.0,
691                        z: 12.0,
692                    },
693                    orientation: euler,
694                    padding: sys::ScsPadding::uninit(),
695                },
696            },
697        };
698
699        let fplacement = unsafe { ValueRef::from_ptr(&raw const fplacement) }
700            .expect("float placement should be present");
701        let dplacement = unsafe { ValueRef::from_ptr(&raw const dplacement) }
702            .expect("double placement should be present");
703        assert_eq!(
704            fplacement.as_fplacement(),
705            Some(FPlacement {
706                position: FVector {
707                    x: 7.0,
708                    y: 8.0,
709                    z: 9.0,
710                },
711                orientation: Euler {
712                    heading: 0.25,
713                    pitch: -0.125,
714                    roll: 0.5,
715                },
716            })
717        );
718        assert_eq!(
719            dplacement.as_dplacement(),
720            Some(DPlacement {
721                position: DVector {
722                    x: 10.0,
723                    y: 11.0,
724                    z: 12.0,
725                },
726                orientation: Euler {
727                    heading: 0.25,
728                    pitch: -0.125,
729                    roll: 0.5,
730                },
731            })
732        );
733    }
734}