use core::fmt;
use crate::frame::Frame;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[non_exhaustive]
pub enum FrameClass {
RootInertial,
PlanetInertial,
BarycenterInertial,
PlanetFixed,
Topocentric,
Body,
OrbitRelative,
External,
}
impl FrameClass {
pub const fn may_be_root_or_integ(self) -> bool {
matches!(
self,
FrameClass::RootInertial | FrameClass::PlanetInertial | FrameClass::BarycenterInertial
)
}
pub const fn is_rotating(self) -> bool {
matches!(
self,
FrameClass::PlanetFixed | FrameClass::Topocentric | FrameClass::OrbitRelative
)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum FrameRoleStatic {
Primary,
AltInertial,
AltPfix,
CoreBody,
CompositeBody,
Structure,
VehiclePoint,
Lvlh,
Ned,
Custom(&'static str),
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[non_exhaustive]
pub enum FrameRole {
Primary,
AltInertial,
AltPfix,
CoreBody,
CompositeBody,
Structure,
VehiclePoint,
Lvlh,
Ned,
Custom(Box<str>),
}
impl FrameRole {
pub fn matches_static(&self, role: FrameRoleStatic) -> bool {
match (self, role) {
(FrameRole::Primary, FrameRoleStatic::Primary)
| (FrameRole::AltInertial, FrameRoleStatic::AltInertial)
| (FrameRole::AltPfix, FrameRoleStatic::AltPfix)
| (FrameRole::CoreBody, FrameRoleStatic::CoreBody)
| (FrameRole::CompositeBody, FrameRoleStatic::CompositeBody)
| (FrameRole::Structure, FrameRoleStatic::Structure)
| (FrameRole::VehiclePoint, FrameRoleStatic::VehiclePoint)
| (FrameRole::Lvlh, FrameRoleStatic::Lvlh)
| (FrameRole::Ned, FrameRoleStatic::Ned) => true,
(FrameRole::Custom(owned), FrameRoleStatic::Custom(expected)) => &**owned == expected,
_ => false,
}
}
}
impl From<FrameRoleStatic> for FrameRole {
fn from(r: FrameRoleStatic) -> Self {
match r {
FrameRoleStatic::Primary => FrameRole::Primary,
FrameRoleStatic::AltInertial => FrameRole::AltInertial,
FrameRoleStatic::AltPfix => FrameRole::AltPfix,
FrameRoleStatic::CoreBody => FrameRole::CoreBody,
FrameRoleStatic::CompositeBody => FrameRole::CompositeBody,
FrameRoleStatic::Structure => FrameRole::Structure,
FrameRoleStatic::VehiclePoint => FrameRole::VehiclePoint,
FrameRoleStatic::Lvlh => FrameRole::Lvlh,
FrameRoleStatic::Ned => FrameRole::Ned,
FrameRoleStatic::Custom(s) => FrameRole::Custom(s.into()),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Namespace(pub u16);
impl Namespace {
pub const LOCAL: Namespace = Namespace(0);
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum Tag {
None,
Named(Box<str>),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum MintPolicy {
Stable,
Wildcard,
PerBodyIntegration,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct FrameDescriptorStatic {
pub class: FrameClass,
pub role: FrameRoleStatic,
pub tag: Option<&'static str>,
pub mint: MintPolicy,
}
impl FrameDescriptorStatic {
pub const fn is_wildcard(&self) -> bool {
matches!(self.mint, MintPolicy::Wildcard)
}
}
pub const fn mint_for_param(is_wildcard: bool) -> MintPolicy {
if is_wildcard {
MintPolicy::Wildcard
} else {
MintPolicy::Stable
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct FrameUid {
pub namespace: Namespace,
pub class: FrameClass,
pub role: FrameRole,
pub tag: Tag,
}
impl FrameUid {
pub fn of<F: Frame>() -> FrameUid {
let d = F::DESCRIPTOR;
match d.mint {
MintPolicy::Stable => FrameUid {
namespace: Namespace::LOCAL,
class: d.class,
role: d.role.into(),
tag: match d.tag {
Some(name) => Tag::Named(name.into()),
None => Tag::None,
},
},
MintPolicy::Wildcard => panic!(
"JEOD_INV TS.01: cannot mint a FrameUid for `{}` (class {:?}): it is a \
runtime-resolved storage-boundary wildcard with no concrete identity. \
Resolve the entity's real vehicle / planet / per-node frame at the \
storage boundary and call `FrameUid::of` on that concrete frame type \
instead.",
F::NAME,
d.class
),
MintPolicy::PerBodyIntegration => panic!(
"RF.10: cannot mint a FrameUid for `IntegrationFrame`: it resolves per \
body to `RootInertial` or `PlanetInertial<P>`. Publish the resolved \
frame's identity — mint `FrameUid::of::<RootInertial>()` or \
`FrameUid::of::<PlanetInertial<P>>()` for the body's actual \
integration frame instead."
),
}
}
pub fn external(ns: Namespace, class: FrameClass, role: FrameRole, tag: Tag) -> FrameUid {
assert!(
ns != Namespace::LOCAL,
"FrameUid::external was handed Namespace::LOCAL, which is reserved for \
type-derived identity (FrameUid::of). Pass a non-zero namespace allocated \
for this producer, or use FrameUid::of::<F>() if the identity comes from \
a sealed Frame type."
);
FrameUid {
namespace: ns,
class,
role,
tag,
}
}
#[must_use]
pub fn with_namespace(mut self, ns: Namespace) -> FrameUid {
self.namespace = ns;
self
}
pub fn is<F: Frame>(&self) -> bool {
let d = F::DESCRIPTOR;
if !matches!(d.mint, MintPolicy::Stable) {
return false;
}
self.namespace == Namespace::LOCAL
&& self.class == d.class
&& self.role.matches_static(d.role)
&& match (&self.tag, d.tag) {
(Tag::Named(name), Some(expected)) => &**name == expected,
(Tag::None, None) => true,
_ => false,
}
}
}
impl fmt::Display for FrameUid {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.namespace != Namespace::LOCAL {
write!(f, "ns{}:", self.namespace.0)?;
}
let suffix: &str = match &self.role {
FrameRole::Custom(role) => role,
FrameRole::AltInertial => "inertial.alt",
FrameRole::AltPfix => "pfix.alt",
FrameRole::CoreBody => "core_body",
FrameRole::CompositeBody => "composite_body",
FrameRole::Structure => "structural",
FrameRole::VehiclePoint => "point",
FrameRole::Lvlh => "lvlh",
FrameRole::Ned => "ned",
FrameRole::Primary => match self.class {
FrameClass::RootInertial
| FrameClass::PlanetInertial
| FrameClass::BarycenterInertial => "inertial",
FrameClass::PlanetFixed => "pfix",
FrameClass::Topocentric => "topo",
FrameClass::Body => "body",
FrameClass::OrbitRelative => "orbit_relative",
FrameClass::External => "external",
},
};
match &self.tag {
Tag::Named(name) => write!(f, "{name}.{suffix}"),
Tag::None => write!(f, "{suffix}"),
}
}
}
#[cfg(test)]
mod class_predicate_tests {
use super::FrameClass;
#[test]
fn root_or_integ_eligibility_covers_exactly_the_inertial_classes() {
let eligible = [
FrameClass::RootInertial,
FrameClass::PlanetInertial,
FrameClass::BarycenterInertial,
];
let ineligible = [
FrameClass::PlanetFixed,
FrameClass::Topocentric,
FrameClass::Body,
FrameClass::OrbitRelative,
FrameClass::External,
];
for c in eligible {
assert!(c.may_be_root_or_integ(), "{c:?} should be root-eligible");
}
for c in ineligible {
assert!(
!c.may_be_root_or_integ(),
"{c:?} should not be root-eligible"
);
}
}
#[test]
fn is_rotating_covers_exactly_the_structurally_rotating_classes() {
let rotating = [
FrameClass::PlanetFixed,
FrameClass::Topocentric,
FrameClass::OrbitRelative,
];
let non_rotating = [
FrameClass::RootInertial,
FrameClass::PlanetInertial,
FrameClass::BarycenterInertial,
FrameClass::Body,
FrameClass::External,
];
for c in rotating {
assert!(c.is_rotating(), "{c:?} should be classified rotating");
}
for c in non_rotating {
assert!(!c.is_rotating(), "{c:?} should not be classified rotating");
}
}
}
#[cfg(all(test, feature = "serde"))]
mod serde_tests {
use super::*;
use crate::frame::{Earth, PlanetInertial, RootInertial};
#[test]
fn type_derived_uid_round_trips() {
let uid = FrameUid::of::<PlanetInertial<Earth>>();
let json = serde_json::to_string(&uid).expect("FrameUid serializes");
let back: FrameUid = serde_json::from_str(&json).expect("FrameUid deserializes");
assert_eq!(uid, back);
}
#[test]
fn external_uid_with_custom_role_round_trips() {
let uid = FrameUid::external(
Namespace(7),
FrameClass::External,
FrameRole::Custom("sensor_boresight".into()),
Tag::Named("ext-probe-42".into()),
);
let json = serde_json::to_string(&uid).expect("FrameUid serializes");
let back: FrameUid = serde_json::from_str(&json).expect("FrameUid deserializes");
assert_eq!(uid, back);
}
#[test]
fn untagged_uid_round_trips() {
let uid = FrameUid::of::<RootInertial>();
assert_eq!(uid.tag, Tag::None);
let json = serde_json::to_string(&uid).expect("FrameUid serializes");
let back: FrameUid = serde_json::from_str(&json).expect("FrameUid deserializes");
assert_eq!(uid, back);
}
}