Another project
0

Configure Feed

Select the types of activity you want to include in your feed.

at main 3.0 kB View raw
1use core::num::NonZeroU32; 2use std::time::Duration; 3 4use bone_ui::input::FrameInstant; 5use serde::{Deserialize, Serialize}; 6 7#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] 8#[serde(try_from = "u32", into = "u32")] 9pub struct FrameCount(NonZeroU32); 10 11#[derive(Copy, Clone, Debug, PartialEq, Eq, thiserror::Error)] 12#[error("frame count must be a positive integer, got {0}")] 13pub struct ZeroFrameCount(u32); 14 15impl FrameCount { 16 pub const ONE: Self = Self(NonZeroU32::MIN); 17 18 #[must_use] 19 pub const fn new(frames: NonZeroU32) -> Self { 20 Self(frames) 21 } 22 23 #[must_use] 24 pub const fn get(self) -> u32 { 25 self.0.get() 26 } 27} 28 29impl TryFrom<u32> for FrameCount { 30 type Error = ZeroFrameCount; 31 32 fn try_from(value: u32) -> Result<Self, Self::Error> { 33 NonZeroU32::new(value) 34 .map(Self) 35 .ok_or(ZeroFrameCount(value)) 36 } 37} 38 39impl From<FrameCount> for u32 { 40 fn from(value: FrameCount) -> Self { 41 value.0.get() 42 } 43} 44 45#[derive(Copy, Clone, Debug, PartialEq, Eq)] 46pub struct FrameClock(FrameInstant); 47 48impl FrameClock { 49 pub const FIXED_STEP: Duration = Duration::from_micros(16_667); 50 51 #[must_use] 52 pub const fn start() -> Self { 53 Self(FrameInstant::ZERO) 54 } 55 56 #[must_use] 57 pub const fn now(self) -> FrameInstant { 58 self.0 59 } 60 61 pub fn feed(&mut self, now: FrameInstant) { 62 self.0 = self.0.max(now); 63 } 64 65 pub fn advance(&mut self, frames: FrameCount) { 66 self.0 = self.0.after(Self::FIXED_STEP.saturating_mul(frames.get())); 67 } 68} 69 70#[cfg(test)] 71mod tests { 72 use super::*; 73 74 fn frames(n: u32) -> FrameCount { 75 match NonZeroU32::new(n) { 76 Some(count) => FrameCount::new(count), 77 None => panic!("frame count must be nonzero"), 78 } 79 } 80 81 #[test] 82 fn advance_steps_exactly_one_fixed_step_per_frame() { 83 let mut clock = FrameClock::start(); 84 clock.advance(frames(60)); 85 assert_eq!( 86 clock.now().duration(), 87 FrameClock::FIXED_STEP * 60, 88 "sixty fixed steps land on a bit-exact instant" 89 ); 90 } 91 92 #[test] 93 fn feed_never_rewinds() { 94 let mut clock = FrameClock::start(); 95 clock.feed(FrameInstant::from_duration(Duration::from_millis(100))); 96 clock.feed(FrameInstant::from_duration(Duration::from_millis(40))); 97 assert_eq!( 98 clock.now(), 99 FrameInstant::from_duration(Duration::from_millis(100)), 100 "a stale feed must not move time backward" 101 ); 102 } 103 104 #[test] 105 fn a_180ms_animation_finishes_on_frame_11() { 106 let finished_at = (1_u32..=20).find(|frame| { 107 let mut clock = FrameClock::start(); 108 clock.advance(frames(*frame)); 109 clock.now().duration() >= Duration::from_millis(180) 110 }); 111 assert_eq!( 112 finished_at, 113 Some(11), 114 "a 180 ms tween completes on a known frame under fixed step" 115 ); 116 } 117}