use serde::{Deserialize, Serialize};
use crate::freshness::{FreshnessGate, GateState, LockGate, LockState};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum OffloadMode {
VisionOnly,
SlamOnly,
Full,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SessionStatus {
pub lock: LockState,
pub target: GateState,
pub pose: GateState,
pub commanding: bool,
}
#[derive(Debug, Clone)]
pub struct OffloadSession {
mode: OffloadMode,
target_gate: FreshnessGate,
pose_gate: FreshnessGate,
lock: LockGate,
}
impl OffloadSession {
pub fn new(mode: OffloadMode, target_budget_ms: i64, pose_budget_ms: i64) -> Self {
Self {
mode,
target_gate: FreshnessGate::new(target_budget_ms),
pose_gate: FreshnessGate::new(pose_budget_ms),
lock: LockGate::new(),
}
}
pub fn mode(&self) -> OffloadMode {
self.mode
}
pub fn on_detection(&mut self, now_ms: i64) {
self.target_gate.record(now_ms);
}
pub fn on_pose(&mut self, now_ms: i64) {
self.pose_gate.record(now_ms);
}
pub fn set_link(&mut self, up: bool) {
self.target_gate.set_link(up);
self.pose_gate.set_link(up);
}
pub fn designate(&mut self) {
self.lock.lock();
}
pub fn drop_lock(&mut self) {
self.lock.unlock();
}
pub fn lock_state(&self) -> LockState {
self.lock.state()
}
fn streams_usable(&self, now_ms: i64) -> bool {
match self.mode {
OffloadMode::VisionOnly => self.target_gate.is_usable(now_ms),
OffloadMode::SlamOnly => self.pose_gate.is_usable(now_ms),
OffloadMode::Full => {
self.target_gate.is_usable(now_ms) && self.pose_gate.is_usable(now_ms)
}
}
}
pub fn tick(&mut self, now_ms: i64) -> SessionStatus {
let usable = self.streams_usable(now_ms);
self.lock.update(usable);
SessionStatus {
lock: self.lock.state(),
target: self.target_gate.state(now_ms),
pose: self.pose_gate.state(now_ms),
commanding: self.lock.is_locked() && usable,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_full_session_commands_when_locked_and_both_streams_fresh() {
let mut s = OffloadSession::new(OffloadMode::Full, 500, 500);
s.designate();
s.on_detection(1000);
s.on_pose(1000);
let status = s.tick(1100);
assert_eq!(status.lock, LockState::Locked);
assert!(status.commanding);
}
#[test]
fn a_full_session_stops_when_one_required_stream_goes_stale() {
let mut s = OffloadSession::new(OffloadMode::Full, 500, 500);
s.designate();
s.on_detection(1000);
s.on_pose(1000);
assert!(s.tick(1100).commanding);
s.on_detection(1500); let status = s.tick(1700); assert_eq!(status.pose, GateState::Stale);
assert_eq!(status.lock, LockState::Lost);
assert!(!status.commanding);
}
#[test]
fn a_link_drop_stops_the_session() {
let mut s = OffloadSession::new(OffloadMode::VisionOnly, 500, 500);
s.designate();
s.on_detection(1000);
assert!(s.tick(1100).commanding);
s.set_link(false);
let status = s.tick(1150);
assert_eq!(status.lock, LockState::Lost);
assert!(!status.commanding);
}
#[test]
fn a_session_never_auto_re_acquires_after_a_stale_drop() {
let mut s = OffloadSession::new(OffloadMode::VisionOnly, 500, 500);
s.designate();
s.on_detection(1000);
assert!(s.tick(1100).commanding);
assert_eq!(s.tick(2000).lock, LockState::Lost);
s.on_detection(2100);
s.on_detection(2200);
let status = s.tick(2250);
assert_eq!(status.target, GateState::Fresh);
assert_eq!(status.lock, LockState::Lost); assert!(!status.commanding);
s.designate();
s.on_detection(2300);
assert!(s.tick(2350).commanding);
}
#[test]
fn slam_only_gates_on_the_pose_stream() {
let mut s = OffloadSession::new(OffloadMode::SlamOnly, 500, 500);
s.designate();
s.on_detection(1000);
assert!(!s.tick(1100).commanding);
s.on_pose(1100);
assert!(s.tick(1200).commanding);
}
#[test]
fn a_session_never_auto_re_acquires_after_a_link_drop() {
let mut s = OffloadSession::new(OffloadMode::VisionOnly, 500, 500);
s.designate();
s.on_detection(1000);
assert!(s.tick(1100).commanding);
s.set_link(false);
assert_eq!(s.tick(1150).lock, LockState::Lost);
s.set_link(true);
s.on_detection(1200);
let status = s.tick(1250);
assert_eq!(status.target, GateState::Fresh);
assert_eq!(status.lock, LockState::Lost);
assert!(!status.commanding);
}
#[test]
fn a_full_session_holds_while_only_one_stream_is_fresh() {
let mut s = OffloadSession::new(OffloadMode::Full, 500, 500);
s.designate();
s.on_detection(1000); let status = s.tick(1100);
assert_eq!(status.lock, LockState::Locked); assert!(!status.commanding);
}
#[test]
fn an_un_designated_session_never_commands() {
let mut s = OffloadSession::new(OffloadMode::Full, 500, 500);
s.on_detection(1000);
s.on_pose(1000);
let status = s.tick(1100);
assert_eq!(status.lock, LockState::Unlocked);
assert!(!status.commanding);
}
}