use std::fmt;
use serde::{Deserialize, Serialize};
#[derive(
Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize,
)]
#[serde(transparent)]
pub struct Tick(pub u64);
impl Tick {
pub const ZERO: Tick = Tick(0);
pub const fn get(self) -> u64 {
self.0
}
pub const fn next(self) -> Tick {
Tick(self.0.saturating_add(1))
}
pub const fn offset(self, n: u64) -> Tick {
Tick(self.0.saturating_add(n))
}
pub const fn until(self, later: Tick) -> u64 {
later.0.saturating_sub(self.0)
}
}
impl fmt::Display for Tick {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
impl From<u64> for Tick {
fn from(index: u64) -> Self {
Tick(index)
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
pub struct TickRange {
pub start: Tick,
pub end: Tick,
}
impl TickRange {
pub fn new(start: Tick, end: Tick) -> Self {
Self {
start,
end: end.max(start),
}
}
pub const fn at(tick: Tick) -> Self {
Self {
start: tick,
end: tick,
}
}
pub const fn one(tick: Tick) -> Self {
Self {
start: tick,
end: Tick(tick.0.saturating_add(1)),
}
}
pub const fn len(&self) -> u64 {
self.start.until(self.end)
}
pub const fn is_empty(&self) -> bool {
self.start.0 >= self.end.0
}
pub const fn contains(&self, tick: Tick) -> bool {
self.start.0 <= tick.0 && tick.0 < self.end.0
}
pub fn join(self, other: TickRange) -> TickRange {
TickRange {
start: self.start.min(other.start),
end: self.end.max(other.end),
}
}
}
impl fmt::Display for TickRange {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}..{}", self.start, self.end)
}
}
impl Serialize for TickRange {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
(self.start, self.end).serialize(serializer)
}
}
impl<'de> Deserialize<'de> for TickRange {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let (start, end) = <(Tick, Tick)>::deserialize(deserializer)?;
Ok(TickRange::new(start, end))
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct TimeBase {
pub rate_hz: u64,
}
impl TimeBase {
pub const fn new(rate_hz: u64) -> Self {
Self { rate_hz }
}
pub fn seconds(&self, tick: Tick) -> f64 {
if self.rate_hz == 0 {
return 0.0;
}
tick.0 as f64 / self.rate_hz as f64
}
pub fn tick_at(&self, seconds: f64) -> Tick {
Tick((seconds * self.rate_hz as f64).round().max(0.0) as u64)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn ranges_are_half_open_and_clamped() {
let range = TickRange::new(Tick(10), Tick(14));
assert_eq!(range.len(), 4);
assert!(range.contains(Tick(10)));
assert!(!range.contains(Tick(14)));
assert!(TickRange::new(Tick(5), Tick(2)).is_empty());
assert_eq!(TickRange::new(Tick(5), Tick(2)).end, Tick(5));
assert_eq!(TickRange::one(Tick(7)).len(), 1);
}
#[test]
fn join_covers_both() {
let joined = TickRange::new(Tick(10), Tick(12)).join(TickRange::at(Tick(20)));
assert_eq!(joined, TickRange::new(Tick(10), Tick(20)));
}
#[test]
fn ranges_serialize_as_pairs() {
let json = serde_json::to_string(&TickRange::new(Tick(1), Tick(3))).expect("serializes");
assert_eq!(json, "[1,3]");
let back: TickRange = serde_json::from_str("[1,3]").expect("parses");
assert_eq!(back, TickRange::new(Tick(1), Tick(3)));
}
#[test]
fn time_base_converts_both_ways() {
let base = TimeBase::new(1_000_000);
assert_eq!(base.seconds(Tick(2_500_000)), 2.5);
assert_eq!(base.tick_at(0.0000015), Tick(2));
assert_eq!(TimeBase::new(0).seconds(Tick(9)), 0.0);
}
}