forked from 1b-agent/opencadstudio-upstream
Avoid full document, cache, and GPU rebuilds for entity deltas so add, delete, history, selection, and live polyline edits stay responsive on large drawings.
97 lines
3.0 KiB
Rust
97 lines
3.0 KiB
Rust
//! Shared performance trace for terminal output and the in-app `PERF` panel.
|
|
|
|
use std::collections::VecDeque;
|
|
use std::fmt;
|
|
use std::sync::atomic::{AtomicBool, Ordering};
|
|
use std::sync::{Mutex, OnceLock};
|
|
|
|
const MAX_LINES: usize = 500;
|
|
|
|
static UI_ENABLED: AtomicBool = AtomicBool::new(false);
|
|
static ENV_ENABLED: OnceLock<bool> = OnceLock::new();
|
|
static LINES: OnceLock<Mutex<VecDeque<String>>> = OnceLock::new();
|
|
#[cfg(not(target_arch = "wasm32"))]
|
|
static STDERR_TX: OnceLock<std::sync::mpsc::SyncSender<String>> = OnceLock::new();
|
|
|
|
fn lines() -> &'static Mutex<VecDeque<String>> {
|
|
LINES.get_or_init(|| Mutex::new(VecDeque::with_capacity(MAX_LINES)))
|
|
}
|
|
|
|
/// True when tracing was requested with `PERF=1` or the in-app panel is open.
|
|
pub fn enabled() -> bool {
|
|
UI_ENABLED.load(Ordering::Relaxed)
|
|
|| *ENV_ENABLED.get_or_init(|| std::env::var_os("PERF").is_some())
|
|
}
|
|
|
|
/// Enable or disable collection driven by the in-app `PERF` command.
|
|
pub fn set_ui_enabled(enabled: bool) {
|
|
UI_ENABLED.store(enabled, Ordering::Relaxed);
|
|
}
|
|
|
|
/// Write one performance line to stderr and retain it for the in-app panel.
|
|
pub fn record(args: fmt::Arguments<'_>) {
|
|
if !enabled() {
|
|
return;
|
|
}
|
|
let line = args.to_string();
|
|
// Terminal writes can block the UI/render thread when its pipe is slow.
|
|
// PERF is diagnostic and must not create the hitch it is measuring.
|
|
#[cfg(not(target_arch = "wasm32"))]
|
|
{
|
|
let tx = STDERR_TX.get_or_init(|| {
|
|
let (tx, rx) = std::sync::mpsc::sync_channel::<String>(1024);
|
|
let _ = std::thread::Builder::new()
|
|
.name("ocs-perf-log".to_string())
|
|
.spawn(move || {
|
|
while let Ok(line) = rx.recv() {
|
|
eprintln!("{line}");
|
|
}
|
|
});
|
|
tx
|
|
});
|
|
let _ = tx.try_send(line.clone());
|
|
}
|
|
#[cfg(target_arch = "wasm32")]
|
|
eprintln!("{line}");
|
|
let mut entries = lines().lock().unwrap_or_else(|e| e.into_inner());
|
|
if entries.len() == MAX_LINES {
|
|
entries.pop_front();
|
|
}
|
|
entries.push_back(line);
|
|
}
|
|
|
|
/// Plain-text snapshot used by the panel and its Copy button.
|
|
pub fn snapshot_text() -> String {
|
|
let entries = lines().lock().unwrap_or_else(|e| e.into_inner());
|
|
entries
|
|
.iter()
|
|
.map(String::as_str)
|
|
.collect::<Vec<_>>()
|
|
.join("\n")
|
|
}
|
|
|
|
/// A short tail for the live HUD. The full 500-line history remains available
|
|
/// to Copy, while rendering a large monolithic text widget on every mouse event
|
|
/// is avoided.
|
|
pub fn snapshot_tail_text(max_lines: usize) -> String {
|
|
let entries = lines().lock().unwrap_or_else(|e| e.into_inner());
|
|
let skip = entries.len().saturating_sub(max_lines);
|
|
entries
|
|
.iter()
|
|
.skip(skip)
|
|
.map(String::as_str)
|
|
.collect::<Vec<_>>()
|
|
.join("\n")
|
|
}
|
|
|
|
pub fn clear() {
|
|
lines().lock().unwrap_or_else(|e| e.into_inner()).clear();
|
|
}
|
|
|
|
#[macro_export]
|
|
macro_rules! perf_record {
|
|
($($arg:tt)*) => {
|
|
$crate::perf::record(format_args!($($arg)*))
|
|
};
|
|
}
|