use std::time::{Duration, Instant};
use crate::domain::process::ActivityState;
pub(super) const OUTPUT_IDLE_TIMEOUT: Duration = Duration::from_secs(1);
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub(super) enum ActivityTracker {
#[default]
Unscheduled,
RecentOutput(Instant),
ExplicitProgress,
}
impl ActivityTracker {
pub(super) fn observe_output(&mut self, now: Instant) -> ActivityState {
if *self != Self::ExplicitProgress {
*self = Self::RecentOutput(now);
}
ActivityState::Working
}
pub(super) fn observe_progress(&mut self, active: bool) -> ActivityState {
if active {
*self = Self::ExplicitProgress;
ActivityState::Working
} else {
*self = Self::Unscheduled;
ActivityState::AwaitingInput
}
}
pub(super) fn observe_attention(&mut self) -> ActivityState {
*self = Self::Unscheduled;
ActivityState::AwaitingInput
}
pub(super) fn reset(&mut self) {
*self = Self::Unscheduled;
}
pub(super) fn deadline(self) -> Option<Instant> {
match self {
Self::RecentOutput(observed_at) => Some(observed_at + OUTPUT_IDLE_TIMEOUT),
Self::Unscheduled | Self::ExplicitProgress => None,
}
}
pub(super) fn expire(&mut self, now: Instant) -> Option<ActivityState> {
if self.deadline().is_some_and(|deadline| deadline <= now) {
*self = Self::Unscheduled;
Some(ActivityState::Idle)
} else {
None
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn ordinary_output_expires_to_idle() {
let observed_at = Instant::now();
let mut tracker = ActivityTracker::default();
assert_eq!(tracker.observe_output(observed_at), ActivityState::Working);
assert_eq!(
tracker.expire(observed_at + OUTPUT_IDLE_TIMEOUT),
Some(ActivityState::Idle)
);
assert_eq!(tracker, ActivityTracker::Unscheduled);
}
#[test]
fn explicit_progress_has_no_ordinary_output_deadline() {
let mut tracker = ActivityTracker::default();
tracker.observe_progress(true);
tracker.observe_output(Instant::now());
assert_eq!(tracker, ActivityTracker::ExplicitProgress);
assert!(tracker.deadline().is_none());
assert_eq!(tracker.expire(Instant::now() + OUTPUT_IDLE_TIMEOUT), None);
}
#[test]
fn attention_clears_an_ordinary_output_deadline() {
let mut tracker = ActivityTracker::default();
tracker.observe_output(Instant::now());
assert_eq!(tracker.observe_attention(), ActivityState::AwaitingInput);
assert_eq!(tracker, ActivityTracker::Unscheduled);
}
}