1use common_base::span;
2use ordered_float::NotNan;
3use std::{
4 collections::VecDeque,
5 time::{Duration, Instant},
6};
7
8pub struct Clock {
22 target_dt: Duration,
24 last_sys_time: Instant,
26 last_dt: Duration,
29 total_tick_time: Duration,
31 last_dts: VecDeque<NotNan<f32>>,
35 last_dts_sorted: Vec<NotNan<f32>>,
36 last_busy_dts: VecDeque<NotNan<f32>>,
37 stats: ClockStats,
38}
39
40pub struct ClockStats {
41 pub average_busy_dt: Duration,
46 pub average_tps: f64,
48 pub median_tps: f64,
50 pub percentile_90_tps: f64,
52 pub percentile_95_tps: f64,
54 pub percentile_99_tps: f64,
56}
57
58const NUMBER_OF_OLD_DELTAS_KEPT: usize = 100;
59const NUMBER_OF_DELTAS_COMPARED: usize = 5;
60
61impl Clock {
62 pub fn new(target_dt: Duration) -> Self {
63 Self {
64 target_dt,
65 last_sys_time: Instant::now(),
66 last_dt: target_dt,
67 total_tick_time: Duration::default(),
68 last_dts: VecDeque::with_capacity(NUMBER_OF_OLD_DELTAS_KEPT),
69 last_dts_sorted: Vec::with_capacity(NUMBER_OF_OLD_DELTAS_KEPT),
70 last_busy_dts: VecDeque::with_capacity(NUMBER_OF_OLD_DELTAS_KEPT),
71 stats: ClockStats::new(&[], &VecDeque::new()),
72 }
73 }
74
75 pub fn set_target_dt(&mut self, target_dt: Duration) { self.target_dt = target_dt; }
76
77 pub fn stats(&self) -> &ClockStats { &self.stats }
78
79 pub fn dt(&self) -> Duration { self.last_dt }
80
81 pub fn get_stable_dt(&self) -> Duration {
82 if self.last_dts.len() >= NUMBER_OF_DELTAS_COMPARED {
83 let mut dts = [0.0; NUMBER_OF_DELTAS_COMPARED];
85 for (i, dt) in self
86 .last_dts
87 .iter()
88 .rev()
89 .take(NUMBER_OF_DELTAS_COMPARED)
90 .enumerate()
91 {
92 dts[i] = **dt;
93 }
94 dts.sort_by_key(|x| ordered_float::OrderedFloat(*x));
95 let stable_dt = Duration::from_secs_f32(dts[NUMBER_OF_DELTAS_COMPARED / 2]);
96
97 if self.last_dt > 2 * stable_dt {
98 tracing::trace!(?self.last_dt, ?self.total_tick_time, "lag spike detected, unusually slow tick");
99 stable_dt
100 } else {
101 self.last_dt
102 }
103 } else {
104 self.last_dt
105 }
106 }
107
108 pub fn tick(&mut self) {
110 span!(_guard, "tick", "Clock::tick");
111 span!(guard, "clock work");
112 let current_sys_time = Instant::now();
113 let busy_delta = current_sys_time.duration_since(self.last_sys_time);
114 self.last_dts_sorted = self.last_dts.iter().copied().collect();
116 self.last_dts_sorted.sort_unstable();
117 self.stats = ClockStats::new(&self.last_dts_sorted, &self.last_busy_dts);
118 drop(guard);
119 if let Some(sleep_dur) = self.target_dt.checked_sub(busy_delta) {
121 spin_sleep::sleep(sleep_dur);
122 }
123
124 let after_sleep_sys_time = Instant::now();
125 self.last_dt = after_sleep_sys_time.duration_since(self.last_sys_time);
126 if self.last_dts.len() >= NUMBER_OF_OLD_DELTAS_KEPT {
127 self.last_dts.pop_front();
128 }
129 if self.last_busy_dts.len() >= NUMBER_OF_OLD_DELTAS_KEPT {
130 self.last_busy_dts.pop_front();
131 }
132 self.last_dts.push_back(
133 NotNan::new(self.last_dt.as_secs_f32())
134 .expect("Duration::as_secs_f32 never returns NaN"),
135 );
136 self.last_busy_dts.push_back(
137 NotNan::new(busy_delta.as_secs_f32()).expect("Duration::as_secs_f32 never returns NaN"),
138 );
139 self.total_tick_time += self.last_dt;
140 self.last_sys_time = after_sleep_sys_time;
141 }
142}
143
144impl ClockStats {
145 fn new(sorted: &[NotNan<f32>], busy_dt_list: &VecDeque<NotNan<f32>>) -> Self {
146 let average_frame_time =
147 sorted.iter().sum::<NotNan<f32>>().into_inner() / sorted.len().max(1) as f32;
148
149 let average_busy_dt = busy_dt_list.iter().sum::<NotNan<f32>>().into_inner()
150 / busy_dt_list.len().max(1) as f32;
151
152 let average_tps = 1.0 / average_frame_time as f64;
153 let (median_tps, percentile_90_tps, percentile_95_tps, percentile_99_tps) =
154 if sorted.len() >= NUMBER_OF_OLD_DELTAS_KEPT {
155 let median_frame_time = *sorted[sorted.len() / 2];
156 let percentile_90_frame_time =
157 *sorted[(NUMBER_OF_OLD_DELTAS_KEPT as f32 * 0.1) as usize];
158 let percentile_95_frame_time =
159 *sorted[(NUMBER_OF_OLD_DELTAS_KEPT as f32 * 0.05) as usize];
160 let percentile_99_frame_time =
161 *sorted[(NUMBER_OF_OLD_DELTAS_KEPT as f32 * 0.01) as usize];
162
163 let median_tps = 1.0 / median_frame_time as f64;
164 let percentile_90_tps = 1.0 / percentile_90_frame_time as f64;
165 let percentile_95_tps = 1.0 / percentile_95_frame_time as f64;
166 let percentile_99_tps = 1.0 / percentile_99_frame_time as f64;
167 (
168 median_tps,
169 percentile_90_tps,
170 percentile_95_tps,
171 percentile_99_tps,
172 )
173 } else {
174 let avg_tps = 1.0 / average_busy_dt as f64;
175 (avg_tps, avg_tps, avg_tps, avg_tps)
176 };
177
178 Self {
179 average_busy_dt: Duration::from_secs_f32(average_busy_dt),
180 average_tps,
181 median_tps,
182 percentile_90_tps,
183 percentile_95_tps,
184 percentile_99_tps,
185 }
186 }
187}