use leptos::prelude::*; use crate::components::layout::{AdminShell, Breadcrumb}; use crate::terminal::{Renderer, TerminalOptions, TerminalPanel, TerminalRenderEvent}; #[component] pub fn TerminalPage() -> impl IntoView { let renderer = RwSignal::new(initial_terminal_renderer()); let terminal_options = Signal::derive(move || TerminalOptions { renderer: renderer.get(), ..TerminalOptions::default() }); let on_render = Callback::new(update_terminal_bench_probe); view! {
} } #[component] fn TerminalRendererSwitch(renderer: RwSignal) -> impl IntoView { view! { } } #[component] fn RendererButton( renderer: RwSignal, value: Renderer, label: &'static str, ) -> impl IntoView { view! { } } #[cfg(target_arch = "wasm32")] fn initial_terminal_renderer() -> Renderer { web_sys::window() .and_then(|window| window.location().search().ok()) .and_then(|query| renderer_from_query(&query)) .unwrap_or_default() } #[cfg(not(target_arch = "wasm32"))] fn initial_terminal_renderer() -> Renderer { Renderer::default() } #[cfg(any(target_arch = "wasm32", test))] fn renderer_from_query(query: &str) -> Option { query .trim_start_matches('?') .split('&') .filter_map(|part| part.split_once('=')) .find_map(|(key, value)| (key == "renderer").then(|| parse_renderer(value)).flatten()) } #[cfg(any(target_arch = "wasm32", test))] fn parse_renderer(value: &str) -> Option { match value.to_ascii_lowercase().as_str() { "webgl" | "web-gl" => Some(Renderer::WebGl), "canvas" | "canvas2d" | "canvas-2d" => Some(Renderer::Canvas), "dom" => Some(Renderer::Dom), _ => None, } } #[cfg(target_arch = "wasm32")] fn update_terminal_bench_probe(event: TerminalRenderEvent) { use wasm_bindgen::JsValue; let Some(window) = web_sys::window() else { return; }; let Ok(probe) = js_sys::Reflect::get(&window, &JsValue::from_str("__terminalBench")) else { return; }; let probe = if probe.is_object() { probe } else { let object = js_sys::Object::new(); let _ = js_sys::Reflect::set(&window, &JsValue::from_str("__terminalBench"), &object); object.into() }; let count = js_sys::Reflect::get(&probe, &JsValue::from_str("renderCount")) .ok() .and_then(|value| value.as_f64()) .unwrap_or(0.0) + 1.0; let mode = if event.mode.is_live() { "live" } else { "history" }; let renderer = format!("{:?}", event.renderer); let webgl_stats = event.webgl_stats; let _ = js_sys::Reflect::set( &probe, &JsValue::from_str("renderCount"), &JsValue::from_f64(count), ); let _ = js_sys::Reflect::set( &probe, &JsValue::from_str("lastRenderAt"), &JsValue::from_f64(js_sys::Date::now()), ); let _ = js_sys::Reflect::set( &probe, &JsValue::from_str("renderer"), &JsValue::from_str(&renderer), ); let _ = js_sys::Reflect::set(&probe, &JsValue::from_str("mode"), &JsValue::from_str(mode)); let _ = js_sys::Reflect::set( &probe, &JsValue::from_str("cols"), &JsValue::from_f64(f64::from(event.cols)), ); let _ = js_sys::Reflect::set( &probe, &JsValue::from_str("rows"), &JsValue::from_f64(f64::from(event.rows)), ); let _ = js_sys::Reflect::set( &probe, &JsValue::from_str("historyRows"), &JsValue::from_f64(event.history_rows as f64), ); let _ = js_sys::Reflect::set( &probe, &JsValue::from_str("screenRows"), &JsValue::from_f64(event.screen_rows as f64), ); if let Some(stats) = webgl_stats { append_webgl_stats(&probe, stats); } } #[cfg(target_arch = "wasm32")] fn append_webgl_stats(probe: &wasm_bindgen::JsValue, stats: crate::terminal::WebGlRenderStats) { use wasm_bindgen::JsCast; use wasm_bindgen::JsValue; let frames = match js_sys::Reflect::get(probe, &JsValue::from_str("webglFrames")) { Ok(value) if js_sys::Array::is_array(&value) => value.unchecked_into::(), _ => { let array = js_sys::Array::new(); let _ = js_sys::Reflect::set(probe, &JsValue::from_str("webglFrames"), &array); array } }; let frame = js_sys::Object::new(); set_f64(&frame, "totalMs", stats.total_ms); set_f64(&frame, "syncDomMs", stats.sync_dom_ms); set_f64(&frame, "backdropMs", stats.backdrop_ms); set_f64(&frame, "collectMs", stats.collect_ms); set_f64(&frame, "backdropTextureMs", stats.backdrop_texture_ms); set_f64(&frame, "fallbackCanvasMs", stats.fallback_canvas_ms); set_f64(&frame, "atlasMs", stats.atlas_ms); set_f64(&frame, "atlasUploadMs", stats.atlas_upload_ms); set_f64(&frame, "vertexBuildMs", stats.vertex_build_ms); set_f64(&frame, "vertexUploadMs", stats.vertex_upload_ms); set_f64(&frame, "drawMs", stats.draw_ms); set_f64(&frame, "cells", stats.cells as f64); set_f64(&frame, "rowCacheHits", stats.row_cache_hits as f64); set_f64(&frame, "rowCacheMisses", stats.row_cache_misses as f64); set_f64(&frame, "vertexCacheHits", stats.vertex_cache_hits as f64); set_f64( &frame, "vertexCacheMisses", stats.vertex_cache_misses as f64, ); set_f64(&frame, "atlasEntries", stats.atlas_entries as f64); set_f64(&frame, "atlasInsertions", stats.atlas_insertions as f64); set_f64(&frame, "atlasResets", stats.atlas_resets as f64); set_bool(&frame, "fallbackFullCanvas", stats.fallback_full_canvas); set_bool(&frame, "glyphDrawFailed", stats.glyph_draw_failed); frames.push(&frame); while frames.length() > 720 { frames.shift(); } } #[cfg(target_arch = "wasm32")] fn set_f64(target: &js_sys::Object, key: &str, value: f64) { use wasm_bindgen::JsValue; let _ = js_sys::Reflect::set(target, &JsValue::from_str(key), &JsValue::from_f64(value)); } #[cfg(target_arch = "wasm32")] fn set_bool(target: &js_sys::Object, key: &str, value: bool) { use wasm_bindgen::JsValue; let _ = js_sys::Reflect::set(target, &JsValue::from_str(key), &JsValue::from_bool(value)); } #[cfg(not(target_arch = "wasm32"))] fn update_terminal_bench_probe(_event: TerminalRenderEvent) {} #[cfg(test)] mod tests { use super::*; #[test] fn renderer_query_parses_supported_values() { assert_eq!( renderer_from_query("?renderer=webgl"), Some(Renderer::WebGl) ); assert_eq!( renderer_from_query("?foo=1&renderer=canvas2d"), Some(Renderer::Canvas) ); assert_eq!(renderer_from_query("?renderer=dom"), Some(Renderer::Dom)); } #[test] fn renderer_query_ignores_unknown_values() { assert_eq!(renderer_from_query("?renderer=auto"), None); assert_eq!(renderer_from_query("?other=webgl"), None); assert_eq!(renderer_from_query(""), None); } }