open_gpui_motion/
frame_host.rs1use crate::{MotionClockSample, MotionFrameDemand};
4use std::time::{Duration, Instant};
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub struct MotionFrameHost {
12 last_elapsed: Duration,
13 last_frame_demand: MotionFrameDemand,
14 requested_frames: u64,
15}
16
17impl MotionFrameHost {
18 pub const fn new() -> Self {
20 Self {
21 last_elapsed: Duration::ZERO,
22 last_frame_demand: MotionFrameDemand::Idle,
23 requested_frames: 0,
24 }
25 }
26
27 pub const fn last_elapsed(&self) -> Duration {
29 self.last_elapsed
30 }
31
32 pub const fn last_frame_demand(&self) -> MotionFrameDemand {
34 self.last_frame_demand
35 }
36
37 pub const fn requested_frames(&self) -> u64 {
39 self.requested_frames
40 }
41
42 pub const fn reset(&mut self) {
44 self.last_elapsed = Duration::ZERO;
45 self.last_frame_demand = MotionFrameDemand::Idle;
46 self.requested_frames = 0;
47 }
48
49 pub fn observe(&mut self, frame_demand: MotionFrameDemand) -> MotionFrameHostUpdate {
51 self.last_frame_demand = frame_demand;
52 if frame_demand.needs_frame() {
53 self.requested_frames = self.requested_frames.saturating_add(1);
54 }
55 MotionFrameHostUpdate {
56 frame_demand,
57 requested_frames: self.requested_frames,
58 }
59 }
60
61 pub fn observe_all(
63 &mut self,
64 demands: impl IntoIterator<Item = MotionFrameDemand>,
65 ) -> MotionFrameHostUpdate {
66 self.observe(MotionFrameDemand::combine_all(demands))
67 }
68
69 pub fn sample_elapsed<T>(
71 &mut self,
72 requested_elapsed: Duration,
73 sample: impl FnOnce(MotionClockSample) -> (T, MotionFrameDemand),
74 ) -> MotionFrameHostSample<T> {
75 let clock = MotionClockSample::from_elapsed(self.last_elapsed, requested_elapsed);
76 self.last_elapsed = clock.elapsed();
77 let (value, frame_demand) = sample(clock);
78 let update = self.observe(frame_demand);
79 MotionFrameHostSample {
80 value,
81 clock,
82 update,
83 }
84 }
85
86 pub fn sample_since<T>(
90 &mut self,
91 started_at: Instant,
92 now: Instant,
93 sample: impl FnOnce(MotionClockSample) -> (T, MotionFrameDemand),
94 ) -> MotionFrameHostSample<T> {
95 self.sample_elapsed(now.saturating_duration_since(started_at), sample)
96 }
97}
98
99impl Default for MotionFrameHost {
100 fn default() -> Self {
101 Self::new()
102 }
103}
104
105#[must_use = "adapter frame updates must be translated into the owner's frame request API"]
107#[derive(Debug, Clone, Copy, PartialEq, Eq)]
108pub struct MotionFrameHostUpdate {
109 frame_demand: MotionFrameDemand,
110 requested_frames: u64,
111}
112
113impl MotionFrameHostUpdate {
114 pub const fn frame_demand(self) -> MotionFrameDemand {
116 self.frame_demand
117 }
118
119 pub const fn should_request_frame(self) -> bool {
121 self.frame_demand.needs_frame()
122 }
123
124 pub const fn requested_frames(self) -> u64 {
126 self.requested_frames
127 }
128}
129
130#[must_use = "frame host samples include the adapter's next-frame decision"]
132#[derive(Debug, Clone, Copy, PartialEq)]
133pub struct MotionFrameHostSample<T> {
134 value: T,
135 clock: MotionClockSample,
136 update: MotionFrameHostUpdate,
137}
138
139impl<T> MotionFrameHostSample<T> {
140 pub const fn value(&self) -> &T {
142 &self.value
143 }
144
145 pub fn into_value(self) -> T {
147 self.value
148 }
149
150 pub const fn clock(&self) -> MotionClockSample {
152 self.clock
153 }
154
155 pub const fn update(&self) -> MotionFrameHostUpdate {
157 self.update
158 }
159
160 pub const fn frame_demand(&self) -> MotionFrameDemand {
162 self.update.frame_demand()
163 }
164
165 pub const fn should_request_frame(&self) -> bool {
167 self.update.should_request_frame()
168 }
169}
170
171#[cfg(test)]
172mod tests {
173 use super::*;
174 use crate::MotionFrameReason;
175
176 #[test]
177 fn idle_demand_does_not_request_frame() {
178 let mut host = MotionFrameHost::new();
179
180 let update = host.observe(MotionFrameDemand::Idle);
181
182 assert!(!update.should_request_frame());
183 assert_eq!(update.frame_demand(), MotionFrameDemand::Idle);
184 assert_eq!(update.requested_frames(), 0);
185 assert_eq!(host.last_frame_demand(), MotionFrameDemand::Idle);
186 }
187
188 #[test]
189 fn active_demand_requests_frame_and_records_count() {
190 let mut host = MotionFrameHost::new();
191 let demand = MotionFrameDemand::NeedsFrame(MotionFrameReason::UpdateRender);
192
193 let first = host.observe(demand);
194 let second = host.observe(demand);
195
196 assert!(first.should_request_frame());
197 assert_eq!(first.requested_frames(), 1);
198 assert!(second.should_request_frame());
199 assert_eq!(second.requested_frames(), 2);
200 assert_eq!(host.last_frame_demand(), demand);
201 }
202
203 #[test]
204 fn combines_many_demands_into_one_adapter_decision() {
205 let mut host = MotionFrameHost::new();
206 let demand = MotionFrameDemand::NeedsFrame(MotionFrameReason::UpdateRender);
207
208 let update = host.observe_all([MotionFrameDemand::Idle, demand, MotionFrameDemand::Idle]);
209
210 assert!(update.should_request_frame());
211 assert_eq!(update.frame_demand(), demand);
212 assert_eq!(update.requested_frames(), 1);
213 }
214
215 #[test]
216 fn sampling_clamps_non_monotonic_elapsed_time() {
217 let mut host = MotionFrameHost::new();
218 let demand = MotionFrameDemand::NeedsFrame(MotionFrameReason::UpdateRender);
219
220 let first =
221 host.sample_elapsed(Duration::from_millis(40), |clock| (clock.elapsed(), demand));
222 let second =
223 host.sample_elapsed(Duration::from_millis(10), |clock| (clock.elapsed(), demand));
224
225 assert_eq!(*first.value(), Duration::from_millis(40));
226 assert_eq!(*second.value(), Duration::from_millis(40));
227 assert!(second.clock().clamped());
228 assert_eq!(second.clock().delta(), Duration::ZERO);
229 assert_eq!(host.last_elapsed(), Duration::from_millis(40));
230 }
231}