pub const MAX_CHANGE_AGE: u32 = u32::MAX / 2;
#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
pub struct Tick(
pub u32,
);
impl Tick {
pub const ZERO: Tick = Tick(0);
pub fn get(self) -> u32 {
self.0
}
pub fn bump(&mut self) -> Tick {
self.0 = self.0.wrapping_add(1);
*self
}
pub(crate) fn is_newer_than(self, other: Tick) -> bool {
(self.0.wrapping_sub(other.0) as i32) > 0
}
pub fn clamp_to(self, now: Tick) -> Tick {
if now.0.wrapping_sub(self.0) > MAX_CHANGE_AGE {
Tick(now.0.wrapping_sub(MAX_CHANGE_AGE))
} else {
self
}
}
}
#[derive(Debug, Default)]
pub struct AtomicTick(core::sync::atomic::AtomicU32);
impl AtomicTick {
pub fn bump(&self) -> Tick {
use core::sync::atomic::Ordering;
Tick(self.0.fetch_add(1, Ordering::Relaxed).wrapping_add(1))
}
pub fn get(&self) -> Tick {
Tick(self.0.load(core::sync::atomic::Ordering::Relaxed))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn default_is_zero() {
assert_eq!(Tick::default(), Tick::ZERO);
}
#[test]
fn bump_advances() {
let mut t = Tick::ZERO;
assert_eq!(t.bump(), Tick(1));
assert_eq!(t.bump(), Tick(2));
assert_eq!(t.get(), 2);
}
#[test]
fn newer_than_is_strict() {
assert!(Tick(2).is_newer_than(Tick(1)));
assert!(!Tick(1).is_newer_than(Tick(1)));
assert!(!Tick(1).is_newer_than(Tick(2)));
}
#[test]
fn newer_than_survives_wraparound() {
assert!(Tick(0).is_newer_than(Tick(u32::MAX)));
assert!(Tick(5).is_newer_than(Tick(u32::MAX - 2)));
assert!(!Tick(u32::MAX).is_newer_than(Tick(0)));
}
#[test]
fn clamp_pulls_stale_ticks_forward() {
let now = Tick(MAX_CHANGE_AGE + 100);
assert_eq!(Tick::ZERO.clamp_to(now), Tick(100));
assert!(now.is_newer_than(Tick::ZERO.clamp_to(now)));
}
#[test]
fn clamp_leaves_recent_ticks_untouched() {
let now = Tick(1000);
assert_eq!(Tick(990).clamp_to(now), Tick(990));
}
}