subtr_actor/collector/
frame_resolution.rs1const FRAME_RESOLUTION_EPSILON_SECONDS: f32 = 1e-4;
2
3#[derive(Debug, Clone, Copy, Default, PartialEq)]
4pub enum StatsFrameResolution {
5 #[default]
6 EveryFrame,
7 TimeStep {
8 seconds: f32,
9 },
10}
11
12#[derive(Debug, Clone, Copy, PartialEq)]
13pub(crate) enum FinalStatsFrameAction {
14 Append { dt: f32 },
15 ReplaceLast { dt: f32 },
16}
17
18#[derive(Debug, Clone, Copy)]
19pub(crate) struct StatsFramePersistenceController {
20 resolution: StatsFrameResolution,
21 next_emit_time: Option<f32>,
22 last_emitted_frame_number: Option<usize>,
23 last_emitted_time: Option<f32>,
24 last_emitted_dt: f32,
25}
26
27impl StatsFramePersistenceController {
28 pub(crate) fn new(resolution: StatsFrameResolution) -> Self {
29 Self {
30 resolution,
31 next_emit_time: None,
32 last_emitted_frame_number: None,
33 last_emitted_time: None,
34 last_emitted_dt: 0.0,
35 }
36 }
37
38 pub(crate) fn on_frame(&mut self, frame_number: usize, current_time: f32) -> Option<f32> {
39 if self.last_emitted_time.is_none() {
40 return Some(self.record_emit(frame_number, current_time));
41 }
42
43 match self.resolution {
44 StatsFrameResolution::EveryFrame => Some(self.record_emit(frame_number, current_time)),
45 StatsFrameResolution::TimeStep { seconds } => {
46 if !seconds.is_finite() || seconds <= 0.0 {
47 return Some(self.record_emit(frame_number, current_time));
48 }
49
50 let next_emit_time = self.next_emit_time.unwrap_or(current_time + seconds);
51 if current_time + FRAME_RESOLUTION_EPSILON_SECONDS < next_emit_time {
52 self.next_emit_time = Some(next_emit_time);
53 return None;
54 }
55
56 let dt = self.record_emit(frame_number, current_time);
57 let mut advanced_next_emit_time = next_emit_time;
58 while advanced_next_emit_time <= current_time + FRAME_RESOLUTION_EPSILON_SECONDS {
59 advanced_next_emit_time += seconds;
60 }
61 self.next_emit_time = Some(advanced_next_emit_time);
62 Some(dt)
63 }
64 }
65 }
66
67 pub(crate) fn final_frame_action(
68 &self,
69 frame_number: usize,
70 current_time: f32,
71 ) -> Option<FinalStatsFrameAction> {
72 let Some(last_emitted_time) = self.last_emitted_time else {
73 return Some(FinalStatsFrameAction::Append { dt: 0.0 });
74 };
75
76 if self.last_emitted_frame_number == Some(frame_number) {
77 return Some(FinalStatsFrameAction::ReplaceLast {
78 dt: self.last_emitted_dt,
79 });
80 }
81
82 Some(FinalStatsFrameAction::Append {
83 dt: (current_time - last_emitted_time).max(0.0),
84 })
85 }
86
87 fn record_emit(&mut self, frame_number: usize, current_time: f32) -> f32 {
88 let dt = self
89 .last_emitted_time
90 .map(|last_time| (current_time - last_time).max(0.0))
91 .unwrap_or(0.0);
92 self.last_emitted_frame_number = Some(frame_number);
93 self.last_emitted_time = Some(current_time);
94 self.last_emitted_dt = dt;
95 self.next_emit_time = match self.resolution {
96 StatsFrameResolution::EveryFrame => None,
97 StatsFrameResolution::TimeStep { seconds } if seconds.is_finite() && seconds > 0.0 => {
98 Some(current_time + seconds)
99 }
100 StatsFrameResolution::TimeStep { .. } => None,
101 };
102 dt
103 }
104}
105
106#[cfg(test)]
107#[path = "frame_resolution_tests.rs"]
108mod tests;