#[cfg(not(target_arch = "wasm32"))]
use std::sync::{
Arc, Mutex, MutexGuard, PoisonError,
atomic::{AtomicBool, Ordering},
};
#[cfg(not(target_arch = "wasm32"))]
use web_time::{Duration, Instant};
#[cfg(not(target_arch = "wasm32"))]
use crate::pipeline_records::{Recorded, ShaderPipelineRecord};
#[cfg(not(target_arch = "wasm32"))]
pub(crate) const FIRST_SCREEN_SPAN: Duration = Duration::from_secs(2);
#[derive(Clone, Default)]
pub(crate) struct PipelineRecorder {
#[cfg(not(target_arch = "wasm32"))]
state: Arc<RecorderState>,
}
#[cfg(not(target_arch = "wasm32"))]
#[derive(Default)]
struct RecorderState {
drawn: Mutex<Drawn>,
first_screen: Mutex<FirstScreen>,
frame_drawn: AtomicBool,
}
#[cfg(not(target_arch = "wasm32"))]
#[derive(Default)]
struct FirstScreen {
started: Option<Instant>,
over: bool,
}
#[cfg(not(target_arch = "wasm32"))]
impl FirstScreen {
fn over(&mut self) -> bool {
self.over |= self
.started
.is_some_and(|started| started.elapsed() > FIRST_SCREEN_SPAN);
self.over
}
}
#[cfg(not(target_arch = "wasm32"))]
#[derive(Default)]
pub(crate) struct Drawn {
pub(crate) shapes: Vec<Recorded<u64>>,
pub(crate) shaders: Vec<Recorded<ShaderPipelineRecord>>,
pub(crate) fixed: Vec<Recorded<&'static str>>,
}
impl PipelineRecorder {
pub(crate) fn during_demand<T>(&self, build: impl FnOnce() -> T) -> T {
#[cfg(not(target_arch = "wasm32"))]
let started = Instant::now();
let value = build();
#[cfg(not(target_arch = "wasm32"))]
self.extend_first_screen(started.elapsed());
value
}
pub(crate) fn begin_frame(&self) {
#[cfg(not(target_arch = "wasm32"))]
self.first_screen().started.get_or_insert_with(Instant::now);
}
pub(crate) fn hold_first_screen(&self) {
#[cfg(not(target_arch = "wasm32"))]
{
let mut first_screen = self.first_screen();
if !first_screen.over() {
first_screen.started = Some(Instant::now());
}
}
}
}
#[cfg(not(target_arch = "wasm32"))]
impl PipelineRecorder {
pub(crate) fn in_first_screen(&self) -> bool {
!self.first_screen().over()
}
fn extend_first_screen(&self, waited: Duration) {
let mut first_screen = self.first_screen();
if !first_screen.over
&& let Some(started) = &mut first_screen.started
{
*started += waited;
}
}
fn first_screen(&self) -> MutexGuard<'_, FirstScreen> {
self.state
.first_screen
.lock()
.unwrap_or_else(PoisonError::into_inner)
}
pub(crate) fn note_shape(&self, key: u64, first_screen: bool) {
note(&mut self.drawn().shapes, key, first_screen);
}
pub(crate) fn note_shader(&self, record: ShaderPipelineRecord, first_screen: bool) {
note(&mut self.drawn().shaders, record, first_screen);
}
pub(crate) fn note_fixed(&self, label: &'static str, first_screen: bool) {
note(&mut self.drawn().fixed, label, first_screen);
}
pub(crate) fn note_frame_drawn(&self) {
if !self.state.frame_drawn.swap(true, Ordering::AcqRel) {
crate::pipeline_disk_cache::note_change();
}
}
pub(crate) fn frame_drawn(&self) -> bool {
self.state.frame_drawn.load(Ordering::Acquire)
}
pub(crate) fn drawn(&self) -> MutexGuard<'_, Drawn> {
self.state
.drawn
.lock()
.unwrap_or_else(PoisonError::into_inner)
}
}
#[cfg(not(target_arch = "wasm32"))]
fn note<T: PartialEq>(records: &mut Vec<Recorded<T>>, entry: T, first_screen: bool) {
match records.iter_mut().find(|record| record.entry == entry) {
Some(record) if first_screen && !record.first_screen => record.first_screen = true,
Some(_) => return,
None => records.push(Recorded {
entry,
age: 0,
first_screen,
}),
}
crate::pipeline_disk_cache::note_change();
}