use std::{
collections::HashSet,
time::{Duration, Instant},
};
use gpui_kit::{App, EntityId, Global, Task, Window};
struct LoopClock {
start: Instant,
views: HashSet<EntityId>,
tick: Option<(Instant, Task<()>)>,
}
impl Global for LoopClock {}
pub(crate) fn phase(period: Duration, fps: u32, window: &Window, cx: &mut App) -> f32 {
let now = cx.background_executor().now();
let due = now + Duration::from_secs(1) / fps;
if !cx.has_global::<LoopClock>() {
cx.set_global(LoopClock {
start: now,
views: HashSet::default(),
tick: None,
});
}
let clock = cx.global_mut::<LoopClock>();
clock.views.insert(window.current_view());
let elapsed = now.saturating_duration_since(clock.start);
let phase = (elapsed.as_secs_f64() / period.as_secs_f64()).fract() as f32;
if clock
.tick
.as_ref()
.is_none_or(|(pending, _)| due < *pending)
{
let task = cx.spawn(async move |cx| {
cx.background_executor().timer(due - now).await;
cx.update(tick);
});
cx.global_mut::<LoopClock>().tick = Some((due, task));
}
phase
}
fn tick(cx: &mut App) {
let clock = cx.global_mut::<LoopClock>();
if let Some((_, task)) = clock.tick.take() {
task.detach();
}
for view in std::mem::take(&mut clock.views) {
cx.notify(view);
}
}