#[cfg(test)]
use mock_instant::global::Instant;
#[cfg(not(test))]
use std::time::Instant;
use std::{
io::{Error, ErrorKind, Result},
ops::{Deref, DerefMut},
};
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum TimerLoop {
#[default]
Infinite,
Fixed(usize),
}
impl From<usize> for TimerLoop {
fn from(count: usize) -> Self {
if count == 0 {
Self::Infinite
} else {
Self::Fixed(count)
}
}
}
#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct TimerCycle {
pub name: String,
pub duration: usize,
}
impl TimerCycle {
pub fn new(name: impl ToString, duration: usize) -> Self {
Self {
name: name.to_string(),
duration,
}
}
}
impl<T: ToString> From<(T, usize)> for TimerCycle {
fn from((name, duration): (T, usize)) -> Self {
Self::new(name, duration)
}
}
#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct TimerCycles(Vec<TimerCycle>);
impl<T: IntoIterator<Item = TimerCycle>> From<T> for TimerCycles {
fn from(cycles: T) -> Self {
Self(cycles.into_iter().collect())
}
}
impl Deref for TimerCycles {
type Target = Vec<TimerCycle>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl DerefMut for TimerCycles {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum TimerState {
Running,
Paused,
#[default]
Stopped,
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum TimerEvent {
Started,
Began(TimerCycle),
Running(TimerCycle),
Set(TimerCycle),
Paused(TimerCycle),
Resumed(TimerCycle),
Ended(TimerCycle),
Stopped,
}
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct TimerConfig {
pub cycles: TimerCycles,
pub cycles_count: TimerLoop,
}
impl TimerConfig {
fn clone_first_cycle(&self) -> Result<TimerCycle> {
self.cycles.first().cloned().ok_or_else(|| {
Error::new(
ErrorKind::NotFound,
"cannot find first cycle from timer config",
)
})
}
}
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct Timer {
pub config: TimerConfig,
pub state: TimerState,
pub cycle: TimerCycle,
pub cycles_count: TimerLoop,
#[serde(skip)]
pub started_at: Option<Instant>,
pub elapsed: usize,
}
impl Timer {
pub fn new(config: TimerConfig) -> Self {
let cycle = config.clone_first_cycle().unwrap();
let cycles_count = config.cycles_count.clone();
Self {
config,
cycle,
cycles_count,
..Default::default()
}
}
pub fn elapsed(&self) -> usize {
self.started_at
.map(|i| i.elapsed().as_secs() as usize)
.unwrap_or_default()
+ self.elapsed
}
pub fn update(&mut self) -> impl IntoIterator<Item = TimerEvent> {
let mut events = Vec::with_capacity(3);
if let TimerState::Running = self.state {
let mut elapsed = self.elapsed();
let (cycles, total_duration) = self.config.cycles.iter().cloned().fold(
(Vec::new(), 0),
|(mut cycles, mut sum), mut cycle| {
cycle.duration += sum;
sum = cycle.duration;
cycles.push(cycle);
(cycles, sum)
},
);
if let TimerLoop::Fixed(cycles_count) = self.cycles_count {
if elapsed >= total_duration * cycles_count {
self.state = TimerState::Stopped;
return events;
}
}
elapsed %= total_duration;
let last_cycle = cycles[cycles.len() - 1].clone();
let next_cycle = cycles
.into_iter()
.fold(None, |next_cycle, mut cycle| match next_cycle {
None if elapsed < cycle.duration => {
cycle.duration -= elapsed;
Some(cycle)
}
_ => next_cycle,
})
.unwrap_or(last_cycle);
events.push(TimerEvent::Running(self.cycle.clone()));
if self.cycle.name != next_cycle.name {
let mut prev_cycle = self.cycle.clone();
prev_cycle.duration = 0;
events.push(TimerEvent::Ended(prev_cycle));
events.push(TimerEvent::Began(next_cycle.clone()));
}
self.cycle = next_cycle;
}
events
}
pub fn start(&mut self) -> impl IntoIterator<Item = TimerEvent> {
let mut events = Vec::with_capacity(2);
if matches!(self.state, TimerState::Stopped) {
self.state = TimerState::Running;
self.cycle = self.config.clone_first_cycle().unwrap();
self.cycles_count = self.config.cycles_count.clone();
self.started_at = Some(Instant::now());
self.elapsed = 0;
events.push(TimerEvent::Started);
events.push(TimerEvent::Began(self.cycle.clone()));
}
events
}
pub fn set(&mut self, duration_secs: usize) -> impl IntoIterator<Item = TimerEvent> {
self.cycle.duration = duration_secs;
Some(TimerEvent::Set(self.cycle.clone()))
}
pub fn pause(&mut self) -> impl IntoIterator<Item = TimerEvent> {
if matches!(self.state, TimerState::Running) {
self.state = TimerState::Paused;
self.elapsed = self.elapsed();
self.started_at = None;
Some(TimerEvent::Paused(self.cycle.clone()))
} else {
None
}
}
pub fn resume(&mut self) -> impl IntoIterator<Item = TimerEvent> {
if matches!(self.state, TimerState::Paused) {
self.state = TimerState::Running;
self.started_at = Some(Instant::now());
Some(TimerEvent::Resumed(self.cycle.clone()))
} else {
None
}
}
pub fn stop(&mut self) -> impl IntoIterator<Item = TimerEvent> {
let mut events = Vec::with_capacity(2);
if matches!(self.state, TimerState::Running) {
self.state = TimerState::Stopped;
events.push(TimerEvent::Ended(self.cycle.clone()));
events.push(TimerEvent::Stopped);
self.cycle = self.config.clone_first_cycle().unwrap();
self.cycles_count = self.config.cycles_count.clone();
self.started_at = None;
self.elapsed = 0;
}
events
}
}
impl Eq for Timer {}
impl PartialEq for Timer {
fn eq(&self, other: &Self) -> bool {
self.state == other.state && self.cycle == other.cycle && self.elapsed() == other.elapsed()
}
}
#[cfg(test)]
mod tests {
use std::time::Duration;
use mock_instant::global::{Instant, MockClock};
use super::*;
fn testing_timer() -> Timer {
Timer {
config: TimerConfig {
cycles: TimerCycles::from([
TimerCycle::new("a", 3),
TimerCycle::new("b", 2),
TimerCycle::new("c", 1),
]),
..Default::default()
},
state: TimerState::Running,
cycle: TimerCycle::new("a", 3),
started_at: Some(Instant::now()),
..Default::default()
}
}
#[test]
fn running_infinite_timer() {
let mut timer = testing_timer();
assert_eq!(timer.state, TimerState::Running);
assert_eq!(timer.cycle, TimerCycle::new("a", 3));
MockClock::advance(Duration::from_secs(2));
timer.update();
assert_eq!(timer.state, TimerState::Running);
assert_eq!(timer.cycle, TimerCycle::new("a", 1));
MockClock::advance(Duration::from_secs(1));
timer.update();
assert_eq!(timer.state, TimerState::Running);
assert_eq!(timer.cycle, TimerCycle::new("b", 2));
MockClock::advance(Duration::from_secs(2));
timer.update();
assert_eq!(timer.state, TimerState::Running);
assert_eq!(timer.cycle, TimerCycle::new("c", 1));
MockClock::advance(Duration::from_secs(1));
timer.update();
assert_eq!(timer.state, TimerState::Running);
assert_eq!(timer.cycle, TimerCycle::new("a", 3));
}
#[test]
fn running_timer_events() {
let mut timer = testing_timer();
let mut events = Vec::new();
MockClock::advance(Duration::from_secs(1));
events.extend(timer.update());
MockClock::advance(Duration::from_secs(1));
events.extend(timer.update());
MockClock::advance(Duration::from_secs(1));
events.extend(timer.update());
MockClock::advance(Duration::from_secs(1));
events.extend(timer.update());
assert_eq!(
events,
vec![
TimerEvent::Running(TimerCycle::new("a", 3)),
TimerEvent::Running(TimerCycle::new("a", 2)),
TimerEvent::Running(TimerCycle::new("a", 1)),
TimerEvent::Ended(TimerCycle::new("a", 0)),
TimerEvent::Began(TimerCycle::new("b", 2)),
TimerEvent::Running(TimerCycle::new("b", 2)),
]
);
}
#[test]
fn paused_timer_not_impacted_by_iterator() {
let mut timer = testing_timer();
timer.state = TimerState::Paused;
let prev_timer = timer.clone();
timer.update();
assert_eq!(prev_timer, timer);
}
#[test]
fn stopped_timer_not_impacted_by_iterator() {
let mut timer = testing_timer();
timer.state = TimerState::Stopped;
let prev_timer = timer.clone();
timer.update();
assert_eq!(prev_timer, timer);
}
#[test]
fn thread_safe_timer() {
let mut timer = Timer::default();
timer.config = testing_timer().config;
timer.cycle = timer.config.clone_first_cycle().unwrap();
timer.cycles_count = timer.config.cycles_count.clone();
let mut events = Vec::new();
assert_eq!(timer.state, TimerState::Stopped);
assert_eq!(timer.cycle, TimerCycle::new("a", 3));
events.extend(timer.start());
events.extend(timer.set(21));
assert_eq!(timer.state, TimerState::Running);
assert_eq!(timer.cycle, TimerCycle::new("a", 21));
events.extend(timer.pause());
assert_eq!(timer.state, TimerState::Paused);
assert_eq!(timer.cycle, TimerCycle::new("a", 21));
events.extend(timer.resume());
assert_eq!(timer.state, TimerState::Running);
assert_eq!(timer.cycle, TimerCycle::new("a", 21));
events.extend(timer.stop());
assert_eq!(timer.state, TimerState::Stopped);
assert_eq!(timer.cycle, TimerCycle::new("a", 3));
assert_eq!(
events,
vec![
TimerEvent::Started,
TimerEvent::Began(TimerCycle::new("a", 3)),
TimerEvent::Set(TimerCycle::new("a", 21)),
TimerEvent::Paused(TimerCycle::new("a", 21)),
TimerEvent::Resumed(TimerCycle::new("a", 21)),
TimerEvent::Ended(TimerCycle::new("a", 21)),
TimerEvent::Stopped,
]
);
}
}