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/// only merges frames the application's own work causes: output of an embedded terminal, messages
9/// of background work, a running animation. A frame the user asked for is never held back — the
10/// echo of a typed character, the tone under the pointer and every other answer to input is drawn
11/// at once, so the limit cannot be felt in the keyboard.
12///
13/// The default draws 60 frames a second locally and 20 over a remote connection
14/// ([`Env::remote`](crate::env::Env::remote)), because on a slow link every frame is a screen
15/// written down a network: a program pouring out lines would otherwise spend the connection on
16/// frames nobody can read apart.
17///
18/// ```
19/// use qframe::prelude::*;
20/// use qframe::runtime::FrameLimit;
21///
22/// // The default: 60 frames a second at the machine, 20 over SSH.
23/// assert_eq!(FrameLimit::default().frames_per_second(false), Some(60));
24/// assert_eq!(FrameLimit::default().frames_per_second(true), Some(20));
25///
26/// // The same number everywhere, or a quieter one on a remote connection.
27/// assert_eq!(FrameLimit::per_second(30).frames_per_second(true), Some(30));
28/// assert_eq!(FrameLimit::per_second(30).remote(10).frames_per_second(true), Some(10));
29///
30/// // Every frame that is wanted is drawn.
31/// assert_eq!(FrameLimit::none().frames_per_second(false), None);
32/// ```
33#[derive(Debug, Clone, Copy, PartialEq, Eq)]
34pub struct FrameLimit {
35 /// Frames a second at the machine the application runs on; `None` is no limit.
36 local: Option<u32>,
37 /// Frames a second over a remote connection; `None` is no limit.
38 remote: Option<u32>,
39}
40
41impl Default for FrameLimit {
42 fn default() -> Self {
43 Self { local: Some(Self::LOCAL), remote: Some(Self::REMOTE) }
44 }
45}
46
47impl FrameLimit {
48 /// Frames a second the default draws locally: as many as a screen at 60 Hz shows.
49 pub const LOCAL: u32 = 60;
50 /// Frames a second the default draws over a remote connection: fast enough to read a
51 /// scrolling program by, cheap enough for a link that carries every frame.
52 pub const REMOTE: u32 = 20;
53
54 /// The same number of frames a second on every connection. A `frames` of zero is no limit,
55 /// the way [`FrameLimit::none`] is; a limit of no frames would draw nothing at all.
56 #[must_use]
57 pub fn per_second(frames: u32) -> Self {
58 let frames = (frames > 0).then_some(frames);
59 Self { local: frames, remote: frames }
60 }
61
62 /// No limit: every frame the application asks for is drawn.
63 #[must_use]
64 pub fn none() -> Self {
65 Self { local: None, remote: None }
66 }
67
68 /// Draws `frames` a second over a remote connection instead, and leaves the local number as
69 /// it is. A `frames` of zero lifts the limit for remote connections.
70 #[must_use]
71 pub fn remote(self, frames: u32) -> Self {
72 Self { remote: (frames > 0).then_some(frames), ..self }
73 }
74
75 /// How many frames a second this limit allows on the connection in hand; `None` is no limit.
76 /// `remote` is what [`Env::remote`](crate::env::Env::remote) reports.
77 #[must_use]
78 pub fn frames_per_second(self, remote: bool) -> Option<u32> {
79 if remote { self.remote } else { self.local }
80 }
81
82 /// The shortest time between two frames this limit allows, or `None` when it allows any.
83 pub(crate) fn gap(self, remote: bool) -> Option<Duration> {
84 self.frames_per_second(remote).map(|frames| Duration::from_secs(1) / frames)
85 }
86}
87
88#[cfg(test)]
89mod tests {
90 use super::*;
91
92 #[test]
93 fn the_default_draws_fewer_frames_over_a_remote_connection() {
94 let limit = FrameLimit::default();
95 assert_eq!(limit.frames_per_second(false), Some(60));
96 assert_eq!(limit.frames_per_second(true), Some(20));
97 assert_eq!(limit.gap(false), Some(Duration::from_nanos(16_666_666)));
98 assert_eq!(limit.gap(true), Some(Duration::from_millis(50)));
99 }
100
101 #[test]
102 fn a_number_given_holds_on_every_connection_until_the_remote_one_is_given_too() {
103 let same = FrameLimit::per_second(30);
104 assert_eq!(same.frames_per_second(false), Some(30));
105 assert_eq!(same.frames_per_second(true), Some(30));
106 let quieter = same.remote(10);
107 assert_eq!(quieter.frames_per_second(false), Some(30), "the local number stays");
108 assert_eq!(quieter.frames_per_second(true), Some(10));
109 }
110
111 #[test]
112 fn no_limit_and_a_limit_of_zero_frames_both_allow_every_frame() {
113 assert_eq!(FrameLimit::none().gap(false), None);
114 assert_eq!(FrameLimit::none().gap(true), None);
115 assert_eq!(FrameLimit::per_second(0).frames_per_second(false), None, "zero frames would draw nothing");
116 assert_eq!(FrameLimit::default().remote(0).frames_per_second(true), None);
117 assert_eq!(FrameLimit::default().remote(0).frames_per_second(false), Some(60), "only the remote one is lifted");
118 }
119}