use std::{
cell::RefCell,
rc::Rc,
time::{Duration, Instant},
};
use gpui_authoring::{App, FocusId, FramePipeline, PreparedRoots, Window, WindowMetrics};
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct PhaseMetrics {
pub frames: usize,
pub evaluate_passes: usize,
pub layout_passes: usize,
pub paint_passes: usize,
pub evaluate: Duration,
pub layout: Duration,
pub paint: Duration,
}
impl PhaseMetrics {
pub fn root_passes(&self) -> Duration {
self.evaluate + self.layout + self.paint
}
}
pub trait FramePipelineExt: FramePipeline + Sized {
fn max_fps(self, max_fps: u32) -> ThrottledPipeline<Self> {
ThrottledPipeline::new(self, max_fps)
}
fn instrumented(self, metrics: Rc<RefCell<PhaseMetrics>>) -> InstrumentedPipeline<Self> {
InstrumentedPipeline::new(self, metrics)
}
}
impl<P: FramePipeline> FramePipelineExt for P {}
pub struct ThrottledPipeline<P> {
inner: P,
min_interval: Duration,
last_frame: Option<Instant>,
}
impl<P> ThrottledPipeline<P> {
pub fn new(inner: P, max_fps: u32) -> Self {
Self {
inner,
min_interval: Duration::from_secs_f64(1.0 / f64::from(max_fps.max(1))),
last_frame: None,
}
}
pub fn inner(&self) -> &P {
&self.inner
}
}
impl<P: FramePipeline> FramePipeline for ThrottledPipeline<P> {
fn should_render(&mut self, is_dirty: bool, metrics: &WindowMetrics) -> bool {
if !self.inner.should_render(is_dirty, metrics) {
return false;
}
let now = Instant::now();
let min_interval = self.min_interval - self.min_interval / 4;
if let Some(last_frame) = self.last_frame
&& now.duration_since(last_frame) < min_interval
{
return false;
}
self.last_frame = Some(now);
true
}
fn begin_frame(&mut self, window: &mut Window<'_>, cx: &mut App) {
self.inner.begin_frame(window, cx);
}
fn evaluate_roots(&mut self, window: &mut Window<'_>, cx: &mut App) -> PreparedRoots {
self.inner.evaluate_roots(window, cx)
}
fn layout_roots(&mut self, window: &mut Window<'_>, roots: &mut PreparedRoots, cx: &mut App) {
self.inner.layout_roots(window, roots, cx);
}
fn paint_roots(&mut self, window: &mut Window<'_>, roots: PreparedRoots, cx: &mut App) {
self.inner.paint_roots(window, roots, cx);
}
fn finish_frame(&mut self, window: &mut Window<'_>, cx: &mut App) {
self.inner.finish_frame(window, cx);
}
fn complete_frame(&mut self, window: &mut Window<'_>, cx: &mut App) -> Option<FocusId> {
self.inner.complete_frame(window, cx)
}
fn end_frame(
&mut self,
window: &mut Window<'_>,
cx: &mut App,
focus_before_listeners: Option<FocusId>,
) {
self.inner.end_frame(window, cx, focus_before_listeners);
}
}
pub struct InstrumentedPipeline<P> {
inner: P,
metrics: Rc<RefCell<PhaseMetrics>>,
}
impl<P> InstrumentedPipeline<P> {
pub fn new(inner: P, metrics: Rc<RefCell<PhaseMetrics>>) -> Self {
Self { inner, metrics }
}
pub fn inner(&self) -> &P {
&self.inner
}
}
impl<P: FramePipeline> FramePipeline for InstrumentedPipeline<P> {
fn should_render(&mut self, is_dirty: bool, metrics: &WindowMetrics) -> bool {
self.inner.should_render(is_dirty, metrics)
}
fn begin_frame(&mut self, window: &mut Window<'_>, cx: &mut App) {
self.metrics.borrow_mut().frames += 1;
self.inner.begin_frame(window, cx);
}
fn evaluate_roots(&mut self, window: &mut Window<'_>, cx: &mut App) -> PreparedRoots {
let start = Instant::now();
let roots = self.inner.evaluate_roots(window, cx);
let mut metrics = self.metrics.borrow_mut();
metrics.evaluate += start.elapsed();
metrics.evaluate_passes += 1;
roots
}
fn layout_roots(&mut self, window: &mut Window<'_>, roots: &mut PreparedRoots, cx: &mut App) {
let start = Instant::now();
self.inner.layout_roots(window, roots, cx);
let mut metrics = self.metrics.borrow_mut();
metrics.layout += start.elapsed();
metrics.layout_passes += 1;
}
fn paint_roots(&mut self, window: &mut Window<'_>, roots: PreparedRoots, cx: &mut App) {
let start = Instant::now();
self.inner.paint_roots(window, roots, cx);
let mut metrics = self.metrics.borrow_mut();
metrics.paint += start.elapsed();
metrics.paint_passes += 1;
}
fn finish_frame(&mut self, window: &mut Window<'_>, cx: &mut App) {
self.inner.finish_frame(window, cx);
}
fn complete_frame(&mut self, window: &mut Window<'_>, cx: &mut App) -> Option<FocusId> {
self.inner.complete_frame(window, cx)
}
fn end_frame(&mut self, window: &mut Window<'_>, cx: &mut App, focus: Option<FocusId>) {
self.inner.end_frame(window, cx, focus);
}
}