use std::time::Instant;
use winit::event_loop::{ActiveEventLoop, ControlFlow};
#[derive(Clone, Copy, Debug)]
pub(crate) struct WakeRequest {
pub(crate) needs_poll: bool,
pub(crate) wake_at: Instant,
pub(crate) idle_sleep_cap: Option<Instant>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) struct WakeDecision {
pub(crate) wake_at: Option<Instant>,
pub(crate) sleep_until: Option<Instant>,
}
pub(crate) fn reduce_wake_requests(requests: &[WakeRequest]) -> Option<WakeDecision> {
let first = requests.first()?;
if requests.iter().any(|r| r.needs_poll) {
return Some(WakeDecision {
wake_at: None,
sleep_until: None,
});
}
let wake_at = requests
.iter()
.map(|r| r.wake_at)
.fold(first.wake_at, Instant::min);
let sleep_until = requests.iter().try_fold(wake_at, |earliest, r| {
r.idle_sleep_cap.map(|cap| earliest.min(cap))
});
Some(WakeDecision {
wake_at: Some(wake_at),
sleep_until,
})
}
pub(crate) fn apply_wake_decision(event_loop: &ActiveEventLoop, decision: WakeDecision) {
if let Some(sleep_until) = decision.sleep_until {
let duration = sleep_until.saturating_duration_since(Instant::now());
if duration > std::time::Duration::from_millis(1) {
std::thread::sleep(duration);
}
}
match decision.wake_at {
Some(wake_at) => event_loop.set_control_flow(ControlFlow::WaitUntil(wake_at)),
None => event_loop.set_control_flow(ControlFlow::Poll),
}
}
#[cfg(test)]
mod tests {
use super::{WakeRequest, reduce_wake_requests};
use std::time::{Duration, Instant};
fn at(base: Instant, ms: u64) -> Instant {
base + Duration::from_millis(ms)
}
fn idle(base: Instant, wake_ms: u64, cap_ms: u64) -> WakeRequest {
WakeRequest {
needs_poll: false,
wake_at: at(base, wake_ms),
idle_sleep_cap: Some(at(base, cap_ms)),
}
}
fn busy(base: Instant, wake_ms: u64) -> WakeRequest {
WakeRequest {
needs_poll: false,
wake_at: at(base, wake_ms),
idle_sleep_cap: None,
}
}
#[test]
fn no_windows_leaves_the_control_flow_alone() {
assert!(reduce_wake_requests(&[]).is_none());
}
#[test]
fn the_deadline_is_the_minimum_not_the_last_window() {
let base = Instant::now();
let decision = reduce_wake_requests(&[
idle(base, 100, 100),
idle(base, 1000, 1000),
idle(base, 500, 500),
])
.expect("a decision for three windows");
assert_eq!(decision.wake_at, Some(at(base, 100)));
}
#[test]
fn the_minimum_holds_whatever_the_iteration_order() {
let base = Instant::now();
let soonest = reduce_wake_requests(&[idle(base, 30, 900), idle(base, 900, 900)])
.expect("decision")
.wake_at;
let reversed = reduce_wake_requests(&[idle(base, 900, 900), idle(base, 30, 900)])
.expect("decision")
.wake_at;
assert_eq!(soonest, reversed);
assert_eq!(soonest, Some(at(base, 30)));
}
#[test]
fn one_window_needing_poll_settles_it_for_all() {
let base = Instant::now();
let polling = WakeRequest {
needs_poll: true,
wake_at: at(base, 1000),
idle_sleep_cap: None,
};
for requests in [
vec![polling, idle(base, 50, 50)],
vec![idle(base, 50, 50), polling],
] {
let decision = reduce_wake_requests(&requests).expect("decision");
assert_eq!(decision.wake_at, None, "Poll must win over any deadline");
assert_eq!(decision.sleep_until, None, "Poll must not be slept through");
}
}
#[test]
fn the_loop_sleeps_only_when_every_window_is_idle() {
let base = Instant::now();
let all_idle =
reduce_wake_requests(&[idle(base, 900, 50), idle(base, 900, 100)]).expect("decision");
assert!(all_idle.sleep_until.is_some());
let one_busy =
reduce_wake_requests(&[idle(base, 900, 50), busy(base, 900)]).expect("decision");
assert_eq!(
one_busy.sleep_until, None,
"a window with a frame in flight must not be slept through"
);
}
#[test]
fn the_sleep_stops_at_the_earliest_cap_or_deadline() {
let base = Instant::now();
let capped =
reduce_wake_requests(&[idle(base, 900, 100), idle(base, 900, 50)]).expect("decision");
assert_eq!(capped.sleep_until, Some(at(base, 50)));
let deadline =
reduce_wake_requests(&[idle(base, 20, 100), idle(base, 900, 50)]).expect("decision");
assert_eq!(deadline.sleep_until, Some(at(base, 20)));
assert_eq!(deadline.wake_at, Some(at(base, 20)));
}
#[test]
fn a_single_window_is_unchanged_by_the_reduction() {
let base = Instant::now();
let decision = reduce_wake_requests(&[idle(base, 1000, 50)]).expect("decision");
assert_eq!(decision.wake_at, Some(at(base, 1000)));
assert_eq!(decision.sleep_until, Some(at(base, 50)));
}
}