Skip to main content

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}