use crate::{MotionClockSample, MotionFrameDemand};
use std::time::{Duration, Instant};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct MotionFrameHost {
last_elapsed: Duration,
last_frame_demand: MotionFrameDemand,
requested_frames: u64,
}
impl MotionFrameHost {
pub const fn new() -> Self {
Self {
last_elapsed: Duration::ZERO,
last_frame_demand: MotionFrameDemand::Idle,
requested_frames: 0,
}
}
pub const fn last_elapsed(&self) -> Duration {
self.last_elapsed
}
pub const fn last_frame_demand(&self) -> MotionFrameDemand {
self.last_frame_demand
}
pub const fn requested_frames(&self) -> u64 {
self.requested_frames
}
pub const fn reset(&mut self) {
self.last_elapsed = Duration::ZERO;
self.last_frame_demand = MotionFrameDemand::Idle;
self.requested_frames = 0;
}
pub fn observe(&mut self, frame_demand: MotionFrameDemand) -> MotionFrameHostUpdate {
self.last_frame_demand = frame_demand;
if frame_demand.needs_frame() {
self.requested_frames = self.requested_frames.saturating_add(1);
}
MotionFrameHostUpdate {
frame_demand,
requested_frames: self.requested_frames,
}
}
pub fn observe_all(
&mut self,
demands: impl IntoIterator<Item = MotionFrameDemand>,
) -> MotionFrameHostUpdate {
self.observe(MotionFrameDemand::combine_all(demands))
}
pub fn sample_elapsed<T>(
&mut self,
requested_elapsed: Duration,
sample: impl FnOnce(MotionClockSample) -> (T, MotionFrameDemand),
) -> MotionFrameHostSample<T> {
let clock = MotionClockSample::from_elapsed(self.last_elapsed, requested_elapsed);
self.last_elapsed = clock.elapsed();
let (value, frame_demand) = sample(clock);
let update = self.observe(frame_demand);
MotionFrameHostSample {
value,
clock,
update,
}
}
pub fn sample_since<T>(
&mut self,
started_at: Instant,
now: Instant,
sample: impl FnOnce(MotionClockSample) -> (T, MotionFrameDemand),
) -> MotionFrameHostSample<T> {
self.sample_elapsed(now.saturating_duration_since(started_at), sample)
}
}
impl Default for MotionFrameHost {
fn default() -> Self {
Self::new()
}
}
#[must_use = "adapter frame updates must be translated into the owner's frame request API"]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct MotionFrameHostUpdate {
frame_demand: MotionFrameDemand,
requested_frames: u64,
}
impl MotionFrameHostUpdate {
pub const fn frame_demand(self) -> MotionFrameDemand {
self.frame_demand
}
pub const fn should_request_frame(self) -> bool {
self.frame_demand.needs_frame()
}
pub const fn requested_frames(self) -> u64 {
self.requested_frames
}
}
#[must_use = "frame host samples include the adapter's next-frame decision"]
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct MotionFrameHostSample<T> {
value: T,
clock: MotionClockSample,
update: MotionFrameHostUpdate,
}
impl<T> MotionFrameHostSample<T> {
pub const fn value(&self) -> &T {
&self.value
}
pub fn into_value(self) -> T {
self.value
}
pub const fn clock(&self) -> MotionClockSample {
self.clock
}
pub const fn update(&self) -> MotionFrameHostUpdate {
self.update
}
pub const fn frame_demand(&self) -> MotionFrameDemand {
self.update.frame_demand()
}
pub const fn should_request_frame(&self) -> bool {
self.update.should_request_frame()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::MotionFrameReason;
#[test]
fn idle_demand_does_not_request_frame() {
let mut host = MotionFrameHost::new();
let update = host.observe(MotionFrameDemand::Idle);
assert!(!update.should_request_frame());
assert_eq!(update.frame_demand(), MotionFrameDemand::Idle);
assert_eq!(update.requested_frames(), 0);
assert_eq!(host.last_frame_demand(), MotionFrameDemand::Idle);
}
#[test]
fn active_demand_requests_frame_and_records_count() {
let mut host = MotionFrameHost::new();
let demand = MotionFrameDemand::NeedsFrame(MotionFrameReason::UpdateRender);
let first = host.observe(demand);
let second = host.observe(demand);
assert!(first.should_request_frame());
assert_eq!(first.requested_frames(), 1);
assert!(second.should_request_frame());
assert_eq!(second.requested_frames(), 2);
assert_eq!(host.last_frame_demand(), demand);
}
#[test]
fn combines_many_demands_into_one_adapter_decision() {
let mut host = MotionFrameHost::new();
let demand = MotionFrameDemand::NeedsFrame(MotionFrameReason::UpdateRender);
let update = host.observe_all([MotionFrameDemand::Idle, demand, MotionFrameDemand::Idle]);
assert!(update.should_request_frame());
assert_eq!(update.frame_demand(), demand);
assert_eq!(update.requested_frames(), 1);
}
#[test]
fn sampling_clamps_non_monotonic_elapsed_time() {
let mut host = MotionFrameHost::new();
let demand = MotionFrameDemand::NeedsFrame(MotionFrameReason::UpdateRender);
let first =
host.sample_elapsed(Duration::from_millis(40), |clock| (clock.elapsed(), demand));
let second =
host.sample_elapsed(Duration::from_millis(10), |clock| (clock.elapsed(), demand));
assert_eq!(*first.value(), Duration::from_millis(40));
assert_eq!(*second.value(), Duration::from_millis(40));
assert!(second.clock().clamped());
assert_eq!(second.clock().delta(), Duration::ZERO);
assert_eq!(host.last_elapsed(), Duration::from_millis(40));
}
}