qframe/runtime/frame_limit.rs
1//! How often the runtime draws: the frame limit an application sets and its defaults.
2
3use std::time::Duration;
4
5/// How many frames a second the runtime draws at most, for a local and for a remote connection.
6///
7/// An application answers with one from [`App::frame_limit`](super::App::frame_limit). The limit
8/// merges frames the application's own work causes: output of an embedded terminal, messages of
9/// background work, a running animation. It never holds back a frame that answers a key, a paste,
10/// a mouse button going down or a button coming up — the echo of a typed character, the answer to
11/// a click and the place a dragged window comes to rest are drawn at once, so the limit cannot be
12/// felt in the keyboard or in a click.
13///
14/// The pointer moving is merged too: a drag, the pointer passing over the screen and the wheel
15/// arrive as fast as the hand moves, and only the latest state is worth a frame. Every one of
16/// those events still reaches the widgets and the application, which apply them all; only the
17/// drawing waits, at most one gap of the limit, and the first motion after a rest that long is
18/// drawn at once. So a two-second window drag at 5 frames a second writes ten frames, not a frame
19/// for every cell the pointer crossed.
20///
21/// The default draws 60 frames a second locally and 20 over a remote connection
22/// ([`Env::remote`](crate::env::Env::remote)), because on a slow link every frame is a screen
23/// written down a network: a program pouring out lines would otherwise spend the connection on
24/// frames nobody can read apart.
25///
26/// ```
27/// use qframe::prelude::*;
28/// use qframe::runtime::FrameLimit;
29///
30/// // The default: 60 frames a second at the machine, 20 over SSH.
31/// assert_eq!(FrameLimit::default().frames_per_second(false), Some(60));
32/// assert_eq!(FrameLimit::default().frames_per_second(true), Some(20));
33///
34/// // The same number everywhere, or a quieter one on a remote connection.
35/// assert_eq!(FrameLimit::per_second(30).frames_per_second(true), Some(30));
36/// assert_eq!(FrameLimit::per_second(30).remote(10).frames_per_second(true), Some(10));
37///
38/// // Every frame that is wanted is drawn.
39/// assert_eq!(FrameLimit::none().frames_per_second(false), None);
40/// ```
41#[derive(Debug, Clone, Copy, PartialEq, Eq)]
42pub struct FrameLimit {
43 /// Frames a second at the machine the application runs on; `None` is no limit.
44 local: Option<u32>,
45 /// Frames a second over a remote connection; `None` is no limit.
46 remote: Option<u32>,
47}
48
49impl Default for FrameLimit {
50 fn default() -> Self {
51 Self { local: Some(Self::LOCAL), remote: Some(Self::REMOTE) }
52 }
53}
54
55impl FrameLimit {
56 /// Frames a second the default draws locally: as many as a screen at 60 Hz shows.
57 pub const LOCAL: u32 = 60;
58 /// Frames a second the default draws over a remote connection: fast enough to read a
59 /// scrolling program by, cheap enough for a link that carries every frame.
60 pub const REMOTE: u32 = 20;
61
62 /// The same number of frames a second on every connection. A `frames` of zero is no limit,
63 /// the way [`FrameLimit::none`] is; a limit of no frames would draw nothing at all.
64 #[must_use]
65 pub fn per_second(frames: u32) -> Self {
66 let frames = (frames > 0).then_some(frames);
67 Self { local: frames, remote: frames }
68 }
69
70 /// No limit: every frame the application asks for is drawn.
71 #[must_use]
72 pub fn none() -> Self {
73 Self { local: None, remote: None }
74 }
75
76 /// Draws `frames` a second over a remote connection instead, and leaves the local number as
77 /// it is. A `frames` of zero lifts the limit for remote connections.
78 #[must_use]
79 pub fn remote(self, frames: u32) -> Self {
80 Self { remote: (frames > 0).then_some(frames), ..self }
81 }
82
83 /// How many frames a second this limit allows on the connection in hand; `None` is no limit.
84 /// `remote` is what [`Env::remote`](crate::env::Env::remote) reports.
85 #[must_use]
86 pub fn frames_per_second(self, remote: bool) -> Option<u32> {
87 if remote { self.remote } else { self.local }
88 }
89
90 /// The shortest time between two frames this limit allows, or `None` when it allows any.
91 pub(crate) fn gap(self, remote: bool) -> Option<Duration> {
92 self.frames_per_second(remote).map(|frames| Duration::from_secs(1) / frames)
93 }
94}
95
96#[cfg(test)]
97mod tests {
98 use super::*;
99
100 #[test]
101 fn the_default_draws_fewer_frames_over_a_remote_connection() {
102 let limit = FrameLimit::default();
103 assert_eq!(limit.frames_per_second(false), Some(60));
104 assert_eq!(limit.frames_per_second(true), Some(20));
105 assert_eq!(limit.gap(false), Some(Duration::from_nanos(16_666_666)));
106 assert_eq!(limit.gap(true), Some(Duration::from_millis(50)));
107 }
108
109 #[test]
110 fn a_number_given_holds_on_every_connection_until_the_remote_one_is_given_too() {
111 let same = FrameLimit::per_second(30);
112 assert_eq!(same.frames_per_second(false), Some(30));
113 assert_eq!(same.frames_per_second(true), Some(30));
114 let quieter = same.remote(10);
115 assert_eq!(quieter.frames_per_second(false), Some(30), "the local number stays");
116 assert_eq!(quieter.frames_per_second(true), Some(10));
117 }
118
119 #[test]
120 fn no_limit_and_a_limit_of_zero_frames_both_allow_every_frame() {
121 assert_eq!(FrameLimit::none().gap(false), None);
122 assert_eq!(FrameLimit::none().gap(true), None);
123 assert_eq!(FrameLimit::per_second(0).frames_per_second(false), None, "zero frames would draw nothing");
124 assert_eq!(FrameLimit::default().remote(0).frames_per_second(true), None);
125 assert_eq!(FrameLimit::default().remote(0).frames_per_second(false), Some(60), "only the remote one is lifted");
126 }
127}