Skip to main content

open_gpui_motion/
frame_host.rs

1//! Adapter-owned frame request helpers.
2
3use crate::{MotionClockSample, MotionFrameDemand};
4use std::time::{Duration, Instant};
5
6/// Renderer-neutral host state for one adapter-owned motion frame source.
7///
8/// The host does not schedule frames by itself. It records the latest motion demand and returns a
9/// small decision object that the owning adapter can translate into its own frame request API.
10#[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    /// Creates an idle frame host.
19    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    /// Returns the last clamped elapsed time observed by this host.
28    pub const fn last_elapsed(&self) -> Duration {
29        self.last_elapsed
30    }
31
32    /// Returns the latest frame demand observed by this host.
33    pub const fn last_frame_demand(&self) -> MotionFrameDemand {
34        self.last_frame_demand
35    }
36
37    /// Returns how many frame requests this host has asked the adapter to issue.
38    pub const fn requested_frames(&self) -> u64 {
39        self.requested_frames
40    }
41
42    /// Resets elapsed time and demand state.
43    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    /// Observes a frame demand and returns the adapter decision for this render pass.
50    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    /// Combines many frame demands and returns the adapter decision for this render pass.
62    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    /// Samples motion from explicit adapter elapsed time and records the returned frame demand.
70    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    /// Samples motion from adapter instants and records the returned frame demand.
87    ///
88    /// If the owner's run epoch changes, call [`Self::reset`] before sampling the new run.
89    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/// Adapter decision produced after a frame host observes motion demand.
106#[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    /// Returns the frame demand that produced this update.
115    pub const fn frame_demand(self) -> MotionFrameDemand {
116        self.frame_demand
117    }
118
119    /// Returns whether the adapter should request another frame.
120    pub const fn should_request_frame(self) -> bool {
121        self.frame_demand.needs_frame()
122    }
123
124    /// Returns the host's cumulative requested-frame count after this update.
125    pub const fn requested_frames(self) -> u64 {
126        self.requested_frames
127    }
128}
129
130/// Value sampled through a frame host plus the host's adapter decision.
131#[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    /// Returns the sampled value.
141    pub const fn value(&self) -> &T {
142        &self.value
143    }
144
145    /// Consumes the sample and returns the sampled value.
146    pub fn into_value(self) -> T {
147        self.value
148    }
149
150    /// Returns the clamped clock used for sampling.
151    pub const fn clock(&self) -> MotionClockSample {
152        self.clock
153    }
154
155    /// Returns the host update produced after sampling.
156    pub const fn update(&self) -> MotionFrameHostUpdate {
157        self.update
158    }
159
160    /// Returns the frame demand that produced this sample.
161    pub const fn frame_demand(&self) -> MotionFrameDemand {
162        self.update.frame_demand()
163    }
164
165    /// Returns whether the adapter should request another frame.
166    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}