all_is_cubes_ui/apps/
time.rs1use all_is_cubes::math::{PositiveSign, ZeroOne, zo64};
2use all_is_cubes::time::{Duration, Instant, TickSchedule};
3#[cfg(doc)]
4use all_is_cubes::universe::Universe;
5
6#[derive(Clone, Debug, Eq, PartialEq)]
10pub struct FrameClock {
11 schedule: TickSchedule,
12
13 last_absolute_time: Option<Instant>,
14
15 render_dirty: bool,
18
19 accumulated_step_time: Duration,
20
21 draw_fps_counter: FpsCounter,
22}
23
24impl FrameClock {
25 pub(crate) const CATCH_UP_STEPS: u32 = 2;
29
30 pub fn new(schedule: TickSchedule) -> Self {
34 Self {
35 schedule,
36 last_absolute_time: None,
37 render_dirty: true,
38 accumulated_step_time: Duration::ZERO,
39 draw_fps_counter: FpsCounter::default(),
40 }
41 }
42
43 #[doc(hidden)] pub fn draw_fps_counter(&self) -> &FpsCounter {
45 &self.draw_fps_counter
46 }
47
48 fn step_length(&self) -> Duration {
49 self.schedule.delta_t()
50 }
51}
52
53impl FrameClock {
54 pub fn advance_to(&mut self, instant: Instant) {
59 if let Some(last_absolute_time) = self.last_absolute_time {
60 let delta = instant.saturating_duration_since(last_absolute_time);
61 self.accumulated_step_time += delta;
62 self.cap_step_time();
63 }
64 self.last_absolute_time = Some(instant);
65 }
66
67 pub fn advance_by(&mut self, duration: Duration) {
69 self.accumulated_step_time += duration;
70 self.cap_step_time();
71 }
72
73 #[must_use]
82 pub fn request_frame(&mut self, time_since_last_frame: Duration) -> bool {
83 let result = self.should_draw();
84 self.did_draw();
85
86 self.advance_by(time_since_last_frame);
87
88 result
89 }
90
91 pub fn next_step_or_draw_time(&self) -> Option<Instant> {
97 Some(self.last_absolute_time? + self.step_length())
98 }
99
100 pub fn should_draw(&self) -> bool {
106 self.render_dirty
107 }
108
109 pub fn did_draw(&mut self) {
111 self.render_dirty = false;
112 self.draw_fps_counter.record_frame();
113 }
114
115 pub fn should_step(&self) -> bool {
121 self.accumulated_step_time >= self.step_length()
122 }
123
124 pub fn did_step(&mut self, schedule: TickSchedule) {
128 self.accumulated_step_time -= self.step_length();
129 self.render_dirty = true;
130 self.schedule = schedule;
131 }
132
133 fn cap_step_time(&mut self) {
134 let cap = self.step_length() * Self::CATCH_UP_STEPS;
135 if self.accumulated_step_time > cap {
136 self.accumulated_step_time = cap;
137 }
138 }
139}
140
141#[derive(Clone, Debug, Eq, PartialEq)]
143#[doc(hidden)] pub struct FpsCounter {
145 average_frame_time_seconds: Option<PositiveSign<f64>>,
146 last_frame: Option<Instant>,
147}
148
149impl FpsCounter {
150 pub const fn new() -> Self {
151 Self {
152 average_frame_time_seconds: None,
153 last_frame: None,
154 }
155 }
156
157 pub fn period_seconds(&self) -> f64 {
158 match self.average_frame_time_seconds {
159 Some(nnt) => nnt.into_inner(),
160 None => f64::NAN,
161 }
162 }
163
164 pub fn frames_per_second(&self) -> f64 {
165 self.period_seconds().recip()
166 }
167}
168
169impl FpsCounter {
170 pub fn record_frame(&mut self) {
171 let this_frame = Instant::now();
172
173 let this_seconds = self
174 .last_frame
175 .and_then(|l| {
176 if this_frame > l {
177 Some(this_frame.saturating_duration_since(l))
178 } else {
179 None
180 }
181 })
182 .and_then(|duration| PositiveSign::try_from(duration.as_secs_f64()).ok());
183 if let Some(this_seconds) = this_seconds {
184 self.average_frame_time_seconds = Some(
185 if let Some(previous) = self.average_frame_time_seconds.filter(|v| v.is_finite()) {
186 const MIX: ZeroOne<f64> = zo64(1.0 / 8.0);
187 this_seconds * MIX + previous * MIX.complement()
188 } else {
189 this_seconds
191 },
192 );
193 }
194 self.last_frame = Some(this_frame);
195 }
196}
197
198impl Default for FpsCounter {
199 fn default() -> Self {
200 Self::new()
201 }
202}