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]
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 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}