use std::sync::atomic::{AtomicBool, AtomicU8, Ordering};
use std::sync::{Mutex, OnceLock};
use std::time::Instant;
type FirstRenderHook = Box<dyn FnOnce() + Send + 'static>;
struct StartupTraceState {
enabled: bool,
started_at: Instant,
first_render_seen: AtomicBool,
first_render_hook: Mutex<Option<FirstRenderHook>>,
}
static STATE: OnceLock<StartupTraceState> = OnceLock::new();
static HOOK_STATE: AtomicU8 = AtomicU8::new(0);
fn state() -> &'static StartupTraceState {
STATE.get_or_init(|| StartupTraceState {
enabled: std::env::var("VTCODE_STARTUP_TRACE").is_ok_and(|value| value == "1"),
started_at: Instant::now(),
first_render_seen: AtomicBool::new(false),
first_render_hook: Mutex::new(None),
})
}
pub fn initialize() {
let _ = state();
}
#[must_use]
pub fn phase_started() -> Option<Instant> {
state().enabled.then(Instant::now)
}
pub fn record_phase(phase: &str, started_at: Option<Instant>) {
let Some(started_at) = started_at else {
return;
};
let elapsed_ms = started_at.elapsed().as_secs_f64() * 1_000.0;
eprintln!("VTCODE_STARTUP_TRACE phase={phase} duration_ms={elapsed_ms:.3}");
}
pub fn install_first_render_hook<F>(hook: F)
where
F: FnOnce() + Send + 'static,
{
let state = state();
if state.first_render_seen.load(Ordering::Acquire) {
hook();
return;
}
let mut pending = state.first_render_hook.lock().unwrap_or_else(|poisoned| poisoned.into_inner());
if state.first_render_seen.load(Ordering::Acquire) {
drop(pending);
hook();
} else {
*pending = Some(Box::new(hook));
HOOK_STATE.store(1, Ordering::Release);
}
}
pub fn record_first_render() {
let state = state();
if !state.enabled && HOOK_STATE.load(Ordering::Acquire) == 0 {
if !state.first_render_seen.load(Ordering::Acquire) {
state.first_render_seen.store(true, Ordering::Release);
}
return;
}
if state.first_render_seen.swap(true, Ordering::AcqRel) {
return;
}
if state.enabled {
let first_render_elapsed_ms = state.started_at.elapsed().as_secs_f64() * 1_000.0;
eprintln!("VTCODE_STARTUP_TRACE phase=first_ui_render duration_ms={first_render_elapsed_ms:.3}");
}
if HOOK_STATE.swap(0, Ordering::AcqRel) == 1 {
let hook = state
.first_render_hook
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner())
.take();
if let Some(hook) = hook {
hook();
}
}
}