use std::sync::atomic::{AtomicU64, Ordering};
use std::time::Duration;
use crate::application::time;
fn epoch() -> time::Instant {
use std::sync::OnceLock;
static EPOCH: OnceLock<time::Instant> = OnceLock::new();
*EPOCH.get_or_init(time::Instant::now)
}
fn now_ms() -> u64 {
epoch().elapsed().as_millis() as u64
}
const NEVER: u64 = u64::MAX;
static SUBMITTED: AtomicU64 = AtomicU64::new(0);
static COMPLETED: AtomicU64 = AtomicU64::new(0);
static OVERRAN: AtomicU64 = AtomicU64::new(0);
static SLOWEST_MS: AtomicU64 = AtomicU64::new(0);
static LAST_TURN_MS: AtomicU64 = AtomicU64::new(NEVER);
static PROBE_SENT_MS: AtomicU64 = AtomicU64::new(NEVER);
static PROBE_BACK_MS: AtomicU64 = AtomicU64::new(NEVER);
static PROBE_ROUND_TRIP_MS: AtomicU64 = AtomicU64::new(NEVER);
pub(crate) fn submitted() {
let _ = epoch();
SUBMITTED.fetch_add(1, Ordering::Relaxed);
}
pub(crate) fn completed(duration: Duration, overran: bool) {
let millis = duration.as_millis() as u64;
COMPLETED.fetch_add(1, Ordering::Relaxed);
LAST_TURN_MS.store(now_ms(), Ordering::Relaxed);
SLOWEST_MS.fetch_max(millis, Ordering::Relaxed);
if overran {
OVERRAN.fetch_add(1, Ordering::Relaxed);
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
pub struct MainThreadStats {
pub running: bool,
pub submitted: u64,
pub completed: u64,
pub outstanding: u64,
pub overran: u64,
pub slowest_ms: u64,
pub since_last_turn: Option<Duration>,
pub probe_outstanding: Option<Duration>,
pub last_probe_round_trip: Option<Duration>,
}
fn age(stamp: &AtomicU64) -> Option<Duration> {
match stamp.load(Ordering::Relaxed) {
NEVER => None,
then => Some(Duration::from_millis(now_ms().saturating_sub(then))),
}
}
pub fn stats() -> MainThreadStats {
let submitted = SUBMITTED.load(Ordering::Relaxed);
let completed = COMPLETED.load(Ordering::Relaxed);
let sent = PROBE_SENT_MS.load(Ordering::Relaxed);
let back = PROBE_BACK_MS.load(Ordering::Relaxed);
MainThreadStats {
running: crate::application::is_main_thread_running(),
submitted,
completed,
outstanding: submitted.saturating_sub(completed),
overran: OVERRAN.load(Ordering::Relaxed),
slowest_ms: SLOWEST_MS.load(Ordering::Relaxed),
since_last_turn: age(&LAST_TURN_MS),
probe_outstanding: if sent == NEVER || (back != NEVER && back >= sent) {
None
} else {
age(&PROBE_SENT_MS)
},
last_probe_round_trip: match PROBE_ROUND_TRIP_MS.load(Ordering::Relaxed) {
NEVER => None,
millis => Some(Duration::from_millis(millis)),
},
}
}
pub fn probe() {
if !crate::application::is_main_thread_running() {
return;
}
let sent = PROBE_SENT_MS.load(Ordering::Relaxed);
let back = PROBE_BACK_MS.load(Ordering::Relaxed);
let in_flight = sent != NEVER && (back == NEVER || back < sent);
if in_flight {
return;
}
let sent_at = now_ms();
PROBE_SENT_MS.store(sent_at, Ordering::Relaxed);
crate::application::submit_to_main_thread("app_window.probe".to_string(), move || {
let back_at = now_ms();
PROBE_ROUND_TRIP_MS.store(back_at.saturating_sub(sent_at), Ordering::Relaxed);
PROBE_BACK_MS.store(back_at, Ordering::Relaxed);
});
}
#[cfg(test)]
mod tests {
use super::*;
#[cfg_attr(target_arch = "wasm32", wasm_lite::wasm_lite_test)]
#[cfg_attr(not(target_arch = "wasm32"), test)]
fn accounting_moves_with_submissions() {
let before = stats();
submitted();
let queued = stats();
assert_eq!(queued.submitted, before.submitted + 1);
assert_eq!(
queued.outstanding,
before.outstanding + 1,
"a submission that has not completed is outstanding"
);
completed(Duration::from_millis(25), true);
let done = stats();
assert_eq!(done.completed, before.completed + 1);
assert_eq!(done.outstanding, before.outstanding);
assert_eq!(done.overran, before.overran + 1);
assert!(
done.slowest_ms >= 25,
"the slowest turn is a high-water mark"
);
assert!(
done.since_last_turn.is_some(),
"a completed turn stamps the clock"
);
}
#[cfg_attr(target_arch = "wasm32", wasm_lite::wasm_lite_test)]
#[cfg_attr(not(target_arch = "wasm32"), test)]
fn probing_before_the_event_loop_is_a_no_op() {
if crate::application::is_main_thread_running() {
return;
}
probe();
assert!(
stats().probe_outstanding.is_none(),
"nothing was sent, so nothing is outstanding"
);
}
}