use std::{
cell::{Cell, RefCell},
rc::{Rc, Weak},
time::Duration,
};
use cranpose_app_shell::AppShell;
use cranpose_render_wgpu::WgpuRenderer;
use wasm_bindgen::{JsCast, closure::Closure};
use web_time::Instant;
const TIMEOUT_IDLE_BUDGET: Duration = Duration::from_millis(12);
pub(crate) struct IdlePrefetch {
app: Weak<RefCell<AppShell<WgpuRenderer>>>,
queued: Cell<bool>,
}
impl IdlePrefetch {
pub(crate) fn new(app: &Rc<RefCell<AppShell<WgpuRenderer>>>) -> Rc<Self> {
Rc::new(Self {
app: Rc::downgrade(app),
queued: Cell::new(false),
})
}
pub(crate) fn queue_after_frame(self: &Rc<Self>, frame_started: Instant) {
if self.queued.get() || !self.has_work() {
return;
}
let Some(window) = crate::web_frame_host::window() else {
return;
};
self.queued.set(true);
if self.ask_idle_period(&window) {
return;
}
let this = Rc::clone(self);
let timeout = Closure::once_into_js(move || {
this.run(frame_started + TIMEOUT_IDLE_BUDGET);
});
if window
.set_timeout_with_callback_and_timeout_and_arguments_0(timeout.unchecked_ref(), 0)
.is_err()
{
self.queued.set(false);
}
}
fn ask_idle_period(self: &Rc<Self>, window: &web_sys::Window) -> bool {
let this = Rc::clone(self);
let idle = Closure::once_into_js(move |deadline: web_sys::IdleDeadline| {
let remaining = Duration::from_secs_f64(deadline.time_remaining().max(0.0) / 1000.0);
this.run(Instant::now() + remaining);
if this.has_work()
&& !this.queued.replace(true)
&& let Some(window) = crate::web_frame_host::window()
&& !this.ask_idle_period(&window)
{
this.queued.set(false);
}
});
window.request_idle_callback(idle.unchecked_ref()).is_ok()
}
fn run(&self, deadline: Instant) {
self.queued.set(false);
if let Some(app) = self.app.upgrade()
&& let Ok(mut app) = app.try_borrow_mut()
{
app.run_idle_prefetch(Some(deadline), |_| false);
}
}
fn has_work(&self) -> bool {
self.app.upgrade().is_some_and(|app| {
app.try_borrow()
.is_ok_and(|app| app.has_idle_prefetch_work())
})
}
}