use std::fmt;
use std::sync::Arc;
use crate::camera_motion::{CameraMotionState, MotionSource};
use crate::ptz::PtzTelemetry;
use crate::transform::GlobalTransformEstimate;
use crate::transition::TransitionPhase;
use crate::validity::ContextValidity;
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub struct ViewEpoch(u64);
impl ViewEpoch {
pub const INITIAL: Self = Self(0);
#[must_use]
pub fn new(val: u64) -> Self {
Self(val)
}
#[must_use]
pub fn next(self) -> Self {
Self(self.0 + 1)
}
#[must_use]
pub fn as_u64(self) -> u64 {
self.0
}
}
impl fmt::Display for ViewEpoch {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "epoch-{}", self.0)
}
}
impl fmt::Debug for ViewEpoch {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "ViewEpoch({})", self.0)
}
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub struct ViewVersion(u64);
impl ViewVersion {
pub const INITIAL: Self = Self(0);
#[must_use]
pub fn new(val: u64) -> Self {
Self(val)
}
#[must_use]
pub fn next(self) -> Self {
Self(self.0 + 1)
}
#[must_use]
pub fn as_u64(self) -> u64 {
self.0
}
#[must_use]
pub fn versions_since(self, other: Self) -> u64 {
self.0.saturating_sub(other.0)
}
}
impl fmt::Display for ViewVersion {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "v{}", self.0)
}
}
impl fmt::Debug for ViewVersion {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "ViewVersion({})", self.0)
}
}
#[derive(Clone, Debug)]
pub struct ViewState {
pub epoch: ViewEpoch,
pub version: ViewVersion,
pub motion: CameraMotionState,
pub motion_source: MotionSource,
pub transition: TransitionPhase,
pub ptz: Option<PtzTelemetry>,
pub global_transform: Option<GlobalTransformEstimate>,
pub validity: ContextValidity,
pub stability_score: f32,
}
impl ViewState {
#[must_use]
pub fn fixed_initial() -> Self {
Self {
epoch: ViewEpoch::INITIAL,
version: ViewVersion::INITIAL,
motion: CameraMotionState::Stable,
motion_source: MotionSource::None,
transition: TransitionPhase::Settled,
ptz: None,
global_transform: None,
validity: ContextValidity::Valid,
stability_score: clamp_unit(1.0),
}
}
#[must_use]
pub fn observed_initial() -> Self {
Self {
epoch: ViewEpoch::INITIAL,
version: ViewVersion::INITIAL,
motion: CameraMotionState::Unknown,
motion_source: MotionSource::None,
transition: TransitionPhase::Settled,
ptz: None,
global_transform: None,
validity: ContextValidity::Degraded {
reason: crate::validity::DegradationReason::Unknown,
},
stability_score: clamp_unit(0.0),
}
}
}
#[derive(Clone, Debug)]
pub struct ViewSnapshot {
inner: Arc<ViewState>,
}
impl ViewSnapshot {
#[must_use]
pub fn new(state: ViewState) -> Self {
Self {
inner: Arc::new(state),
}
}
#[must_use]
pub fn epoch(&self) -> ViewEpoch {
self.inner.epoch
}
#[must_use]
pub fn version(&self) -> ViewVersion {
self.inner.version
}
#[must_use]
pub fn motion(&self) -> &CameraMotionState {
&self.inner.motion
}
#[must_use]
pub fn motion_source(&self) -> &MotionSource {
&self.inner.motion_source
}
#[must_use]
pub fn transition(&self) -> TransitionPhase {
self.inner.transition
}
#[must_use]
pub fn ptz(&self) -> Option<&PtzTelemetry> {
self.inner.ptz.as_ref()
}
#[must_use]
pub fn global_transform(&self) -> Option<&GlobalTransformEstimate> {
self.inner.global_transform.as_ref()
}
#[must_use]
pub fn validity(&self) -> &ContextValidity {
&self.inner.validity
}
#[must_use]
pub fn stability_score(&self) -> f32 {
self.inner.stability_score
}
#[must_use]
pub fn as_view_state(&self) -> &ViewState {
&self.inner
}
}
fn clamp_unit(v: f32) -> f32 {
if v.is_finite() {
v.clamp(0.0, 1.0)
} else {
0.0
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn view_epoch_initial_is_zero() {
assert_eq!(ViewEpoch::INITIAL.as_u64(), 0);
}
#[test]
fn view_epoch_next_increments() {
let e = ViewEpoch::INITIAL;
assert_eq!(e.next().as_u64(), 1);
assert_eq!(e.next().next().as_u64(), 2);
}
#[test]
fn view_epoch_ordering() {
let a = ViewEpoch::new(3);
let b = ViewEpoch::new(5);
assert!(a < b);
assert_eq!(a, ViewEpoch::new(3));
}
#[test]
fn view_epoch_display() {
assert_eq!(format!("{}", ViewEpoch::new(7)), "epoch-7");
}
#[test]
fn view_version_initial_is_zero() {
assert_eq!(ViewVersion::INITIAL.as_u64(), 0);
}
#[test]
fn view_version_next_increments() {
assert_eq!(ViewVersion::INITIAL.next().as_u64(), 1);
}
#[test]
fn view_version_versions_since() {
let v5 = ViewVersion::new(5);
let v2 = ViewVersion::new(2);
assert_eq!(v5.versions_since(v2), 3);
assert_eq!(v2.versions_since(v5), 0);
}
#[test]
fn fixed_initial_is_stable_valid() {
let vs = ViewState::fixed_initial();
assert_eq!(vs.epoch, ViewEpoch::INITIAL);
assert_eq!(vs.motion, CameraMotionState::Stable);
assert_eq!(vs.validity, ContextValidity::Valid);
assert_eq!(vs.stability_score, 1.0);
assert_eq!(vs.transition, TransitionPhase::Settled);
}
#[test]
fn observed_initial_is_unknown_degraded() {
let vs = ViewState::observed_initial();
assert_eq!(vs.motion, CameraMotionState::Unknown);
assert!(matches!(vs.validity, ContextValidity::Degraded { .. }));
assert_eq!(vs.stability_score, 0.0);
}
#[test]
fn snapshot_reflects_state() {
let vs = ViewState::fixed_initial();
let snap = ViewSnapshot::new(vs);
assert_eq!(snap.epoch(), ViewEpoch::INITIAL);
assert_eq!(snap.stability_score(), 1.0);
assert_eq!(snap.transition(), TransitionPhase::Settled);
}
#[test]
fn snapshot_clone_is_cheap() {
let snap = ViewSnapshot::new(ViewState::fixed_initial());
let snap2 = snap.clone();
assert!(std::ptr::eq(snap.as_view_state(), snap2.as_view_state()));
}
}