Repository: alacritty/alacritty
File: alacritty/src/display/mod.rs (Rust)

Code before:

    /// The refresh rate we've used to compute the `swap_delay`.
    refresh_interval: Duration,
}

impl FrameTimerInfo {
    /// Compute the delay that we should use to achieve the target frame
    /// rate.
    pub fn compute_delay(&mut self, refresh_interval: Duration) -> Duration {
        // If the refresh rate has changed, reset the timings.
        if self.refresh_interval != refresh_interval || self.swap_delay.is_none() {
            self.refresh_interval = refresh_interval;
            self.frame_timestamp = Some(Instant::now());
            self.swap_delay = Some(refresh_interval);
            return refresh_interval;
        }

        // At this point we know that `swap_delay` has some value.
        let swap_delay = self.swap_delay.unwrap();

        let new_swap_delay = match self.frame_timestamp {
            Some(last_frame_timestamp) => {
                // Compute the error.
                let error = self.refresh_interval.as_micros() as i64
                    - last_frame_timestamp.elapsed().as_micros() as i64;
                let new_swap_delay = swap_delay.as_micros() as i64 + error;
                Duration::from_micros(u64::try_from(new_swap_delay).unwrap_or_default())
            },
            // If we don't have any information about the last frame, just return
            // previous target.
            None => swap_delay,
        };

        self.swap_delay = Some(new_swap_delay);
        self.frame_timestamp = Some(Instant::now());

        new_swap_delay
    }
}

/// Calculate the cell dimensions based on font metrics.
///
/// This will return a tuple of the cell width and height.
#[inline]
fn compute_cell_size(config: &UiConfig, metrics: &crossfont::Metrics) -> (f32, f32) {
    let offset_x = f64::from(config.font.offset.x);
    let offset_y = f64::from(config.font.offset.y);
    (
        (metrics.average_advance + offset_x).floor().max(1.) as f32,
        (metrics.line_height + offset_y).floor().max(1.) as f32,
    )

Review comment (suggestion):
Assuming that a frame took a particularly long time, that will mean that immediately the next frame will get hurt a lot too. But I don't think there's any immediate connection between the last frame and the next frame, right?

So shouldn't we try to "stabilize" towards some rolling average rather than flip-flopping between a massive and tiny delay?

What you seem to be doing is basically just to try and **start** rendering every *refresh_rate* ms, while realistically what I think we'd want is to try and **finish** rendering every *refresh_rate* ms.

So I feel like we should add a rolling average to this and subtract it as a "buffer" for how long we'd expect rendering to take. Then keep your logic to adjust for when that buffer was insufficient or to much to ensure we're staying on track. But that way we should be able to aim to finish in time rather than starting in time.

Code after:
}

/// The frame timer state.
pub struct FrameTimer {
    /// Base timestamp used to compute sync points.
    base: Instant,

    /// The last timestamp we synced to.
    last_synced_timestamp: Instant,

    /// The refresh rate we've used to compute sync timestamps.
    refresh_interval: Duration,
}

impl FrameTimer {
    pub fn new() -> Self {
        let now = Instant::now();
        Self { base: now, last_synced_timestamp: now, refresh_interval: Duration::ZERO }
    }

    /// Compute the delay that we should use to achieve the target frame
    /// rate.
    pub fn compute_timeout(&mut self, refresh_interval: Duration) -> Duration {
        let now = Instant::now();

        // Handle refresh rate change.
        if self.refresh_interval != refresh_interval {
            self.base = now;
            self.last_synced_timestamp = now;
            self.refresh_interval = refresh_interval;
            return refresh_interval;
        }

        let next_frame = self.last_synced_timestamp + self.refresh_interval;

        if next_frame < now {
            // Redraw immediately if we haven't drawn in over `refresh_interval` microseconds.
            let elapsed_micros = (now - self.base).as_micros() as u64;
            let refresh_micros = self.refresh_interval.as_micros() as u64;
            self.last_synced_timestamp =
                now - Duration::from_micros(elapsed_micros % refresh_micros);
            Duration::ZERO
        } else {
            // Redraw on the next `refresh_interval` clock tick.
            self.last_synced_timestamp = next_frame;
            next_frame - now
        }
    }
}

/// Calculate the cell dimensions based on font metrics.