1use core::ffi::CStr;
2use core::fmt;
3
4use crate::{TelemetryApiVersion, sys};
5
6mod sealed {
7 pub trait Sealed {}
8}
9
10#[derive(Clone, Copy, Debug, Default, PartialEq)]
16pub struct FVector {
17 pub x: f32,
18 pub y: f32,
19 pub z: f32,
20}
21
22#[derive(Clone, Copy, Debug, Default, PartialEq)]
27pub struct DVector {
28 pub x: f64,
29 pub y: f64,
30 pub z: f64,
31}
32
33#[derive(Clone, Copy, Debug, Default, PartialEq)]
40pub struct Euler {
41 pub heading: f32,
42 pub pitch: f32,
43 pub roll: f32,
44}
45
46#[derive(Clone, Copy, Debug, Default, PartialEq)]
48pub struct FPlacement {
49 pub position: FVector,
50 pub orientation: Euler,
51}
52
53#[derive(Clone, Copy, Debug, Default, PartialEq)]
59pub struct DPlacement {
60 pub position: DVector,
61 pub orientation: Euler,
62}
63
64#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
67pub struct StringValue;
68
69#[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 #[must_use]
177 pub const fn raw(self) -> sys::ScsValueType {
178 self as sys::ScsValueType
179 }
180
181 #[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
208pub trait SdkValue: sealed::Sealed {
214 type Decoded<'a>;
215
216 const VALUE_TYPE: ValueType;
218
219 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 #[must_use]
308 pub unsafe fn from_ptr(value: *const sys::ScsValue) -> Option<Self> {
309 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 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 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 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 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 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 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 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 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 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 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 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 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 let pointer = unsafe { self.raw.value.value_string.value };
454 if pointer.is_null() {
455 return None;
456 }
457
458 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 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}