use std::{
future::Future,
pin::Pin,
task::{Context, Poll},
};
use crate::{
FrameCallbackId,
runtime::{FrameCallbackKind, RuntimeHandle},
};
#[derive(Clone)]
pub struct FrameClock {
runtime: RuntimeHandle,
}
impl FrameClock {
pub fn new(runtime: RuntimeHandle) -> Self {
Self { runtime }
}
pub fn runtime_handle(&self) -> RuntimeHandle {
self.runtime.clone()
}
pub fn with_frame_nanos(
&self,
callback: impl FnOnce(u64) + 'static,
) -> FrameCallbackRegistration {
self.with_frame_nanos_kind(FrameCallbackKind::Transient, callback)
}
pub fn with_perpetual_frame_nanos(
&self,
callback: impl FnOnce(u64) + 'static,
) -> FrameCallbackRegistration {
self.with_frame_nanos_kind(FrameCallbackKind::Perpetual, callback)
}
fn with_frame_nanos_kind(
&self,
kind: FrameCallbackKind,
callback: impl FnOnce(u64) + 'static,
) -> FrameCallbackRegistration {
let id = match kind {
FrameCallbackKind::Transient => self.runtime.register_frame_callback(callback),
FrameCallbackKind::Perpetual => {
self.runtime.register_perpetual_frame_callback(callback)
}
};
match id {
Some(id) => FrameCallbackRegistration::new(self.runtime.clone(), id),
None => FrameCallbackRegistration::inactive(self.runtime.clone()),
}
}
pub fn with_frame_millis(
&self,
callback: impl FnOnce(u64) + 'static,
) -> FrameCallbackRegistration {
self.with_frame_nanos(move |nanos| {
let millis = nanos / 1_000_000;
callback(millis);
})
}
pub fn next_frame(&self) -> NextFrame {
NextFrame::new(self.clone(), FrameCallbackKind::Transient)
}
pub fn next_perpetual_frame(&self) -> NextFrame {
NextFrame::new(self.clone(), FrameCallbackKind::Perpetual)
}
}
pub struct FrameCallbackRegistration {
runtime: RuntimeHandle,
id: Option<FrameCallbackId>,
}
pub struct NextFrame {
clock: FrameClock,
kind: FrameCallbackKind,
registration: Option<FrameCallbackRegistration>,
}
impl NextFrame {
fn new(clock: FrameClock, kind: FrameCallbackKind) -> Self {
Self {
clock,
kind,
registration: None,
}
}
}
impl Future for NextFrame {
type Output = u64;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let this = self.get_mut();
let runtime = &this.clock.runtime;
let Some(registration) = &mut this.registration else {
this.registration = Some(
match runtime.register_frame_waker(this.kind, cx.waker().clone()) {
Some(id) => FrameCallbackRegistration::new(runtime.clone(), id),
None => FrameCallbackRegistration::inactive(runtime.clone()),
},
);
return Poll::Pending;
};
let Some(id) = registration.id else {
return Poll::Pending;
};
match runtime.frame_waker_time(id, cx.waker()) {
Some(time) => {
registration.id = None;
Poll::Ready(time)
}
None => Poll::Pending,
}
}
}
impl FrameCallbackRegistration {
fn new(runtime: RuntimeHandle, id: FrameCallbackId) -> Self {
Self {
runtime,
id: Some(id),
}
}
fn inactive(runtime: RuntimeHandle) -> Self {
Self { runtime, id: None }
}
pub fn cancel(mut self) {
if let Some(id) = self.id.take() {
self.runtime.cancel_frame_callback(id);
}
}
}
impl Drop for FrameCallbackRegistration {
fn drop(&mut self) {
if let Some(id) = self.id.take() {
self.runtime.cancel_frame_callback(id);
}
}
}
#[cfg(test)]
#[path = "tests/frame_clock_tests.rs"]
mod tests;