Detailed changes
@@ -1,4 +1,4 @@
-use crate::{App, PlatformDispatcher};
+use crate::{App, Bigus, PlatformDispatcher};
use async_task::Runnable;
use futures::channel::mpsc;
use smol::prelude::*;
@@ -63,7 +63,7 @@ enum TaskState<T> {
Ready(Option<T>),
/// A task that is currently running.
- Spawned(async_task::Task<T>),
+ Spawned(async_task::Task<T, Bigus>),
}
impl<T> Task<T> {
@@ -168,8 +168,13 @@ impl BackgroundExecutor {
label: Option<TaskLabel>,
) -> Task<R> {
let dispatcher = self.dispatcher.clone();
- let (runnable, task) =
- async_task::spawn(future, move |runnable| dispatcher.dispatch(runnable, label));
+ let location = core::panic::Location::caller();
+ let (runnable, task) = async_task::Builder::new()
+ .metadata(Bigus { location })
+ .spawn(
+ move |_| future,
+ move |runnable| dispatcher.dispatch(runnable, label),
+ );
runnable.schedule();
Task(TaskState::Spawned(task))
}
@@ -358,10 +363,13 @@ impl BackgroundExecutor {
if duration.is_zero() {
return Task::ready(());
}
- let (runnable, task) = async_task::spawn(async move {}, {
- let dispatcher = self.dispatcher.clone();
- move |runnable| dispatcher.dispatch_after(duration, runnable)
- });
+ let location = core::panic::Location::caller();
+ let (runnable, task) = async_task::Builder::new()
+ .metadata(Bigus { location })
+ .spawn(move |_| async move {}, {
+ let dispatcher = self.dispatcher.clone();
+ move |runnable| dispatcher.dispatch_after(duration, runnable)
+ });
runnable.schedule();
Task(TaskState::Spawned(task))
}
@@ -468,24 +476,29 @@ impl ForegroundExecutor {
/// Enqueues the given Task to run on the main thread at some point in the future.
#[track_caller]
- pub fn spawn<R>(&self, future: impl Future<Output = R> + 'static) -> Task<R>
+ pub fn spawn<F, R>(&self, future: F) -> Task<R>
where
+ F: Future<Output = R> + 'static,
R: 'static,
{
let dispatcher = self.dispatcher.clone();
+ let location = core::panic::Location::caller();
#[track_caller]
fn inner<R: 'static>(
dispatcher: Arc<dyn PlatformDispatcher>,
future: AnyLocalFuture<R>,
+ location: &'static core::panic::Location<'static>,
) -> Task<R> {
- let (runnable, task) = spawn_local_with_source_location(future, move |runnable| {
- dispatcher.dispatch_on_main_thread(runnable)
- });
+ let (runnable, task) = spawn_local_with_source_location(
+ future,
+ move |runnable| dispatcher.dispatch_on_main_thread(runnable),
+ Bigus { location },
+ );
runnable.schedule();
Task(TaskState::Spawned(task))
}
- inner::<R>(dispatcher, Box::pin(future))
+ inner::<R>(dispatcher, Box::pin(future), location)
}
}
@@ -494,14 +507,16 @@ impl ForegroundExecutor {
/// Copy-modified from:
/// <https://github.com/smol-rs/async-task/blob/ca9dbe1db9c422fd765847fa91306e30a6bb58a9/src/runnable.rs#L405>
#[track_caller]
-fn spawn_local_with_source_location<Fut, S>(
+fn spawn_local_with_source_location<Fut, S, M>(
future: Fut,
schedule: S,
-) -> (Runnable<()>, async_task::Task<Fut::Output, ()>)
+ metadata: M,
+) -> (Runnable<M>, async_task::Task<Fut::Output, M>)
where
Fut: Future + 'static,
Fut::Output: 'static,
- S: async_task::Schedule<()> + Send + Sync + 'static,
+ S: async_task::Schedule<M> + Send + Sync + 'static,
+ M: 'static,
{
#[inline]
fn thread_id() -> ThreadId {
@@ -549,7 +564,11 @@ where
location: Location::caller(),
};
- unsafe { async_task::spawn_unchecked(future, schedule) }
+ unsafe {
+ async_task::Builder::new()
+ .metadata(metadata)
+ .spawn_unchecked(move |_| future, schedule)
+ }
}
/// Scope manages a set of tasks that are enqueued and waited on together. See [`BackgroundExecutor::scoped`].
@@ -71,6 +71,7 @@ pub use app::*;
pub(crate) use arena::*;
pub use asset_cache::*;
pub use assets::*;
+use collections::HashMap;
pub use color::*;
pub use ctor::ctor;
pub use element::*;
@@ -85,6 +86,7 @@ pub use inspector::*;
pub use interactive::*;
use key_dispatch::*;
pub use keymap::*;
+use parking_lot::Mutex;
pub use path_builder::*;
pub use platform::*;
pub use refineable::*;
@@ -107,7 +109,12 @@ pub use util::{FutureExt, Timeout, arc_cow::ArcCow};
pub use view::*;
pub use window::*;
-use std::{any::Any, borrow::BorrowMut, future::Future};
+use std::{
+ any::Any,
+ borrow::BorrowMut,
+ future::Future,
+ sync::{Arc, LazyLock, atomic::AtomicUsize},
+};
use taffy::TaffyLayoutEngine;
/// The context trait, allows the different contexts in GPUI to be used
@@ -309,3 +316,43 @@ pub struct GpuSpecs {
/// Further information about the driver, as reported by Vulkan.
pub driver_info: String,
}
+
+pub(crate) static FRAME_INDEX: AtomicUsize = AtomicUsize::new(0);
+
+/// A
+#[derive(Debug, Clone)]
+pub struct FrameTimings {
+ /// A
+ pub frame_time: f64,
+ /// A
+ pub timings: HashMap<&'static core::panic::Location<'static>, f64>,
+}
+
+/// TESTING
+pub static FRAME_RING: usize = 240;
+pub(crate) static FRAME_BUF: LazyLock<[Arc<Mutex<FrameTimings>>; FRAME_RING]> =
+ LazyLock::new(|| {
+ core::array::from_fn(|_| {
+ Arc::new(Mutex::new(FrameTimings {
+ frame_time: 0.0,
+ timings: HashMap::default(),
+ }))
+ })
+ });
+
+/// A
+pub fn get_frame_timings() -> FrameTimings {
+ FRAME_BUF
+ [(FRAME_INDEX.load(std::sync::atomic::Ordering::Acquire) % FRAME_RING).saturating_sub(1)]
+ .lock()
+ .clone()
+}
+
+/// A
+pub fn get_all_timings() -> (Vec<FrameTimings>, usize) {
+ let frame_index = FRAME_INDEX.load(std::sync::atomic::Ordering::Acquire) % FRAME_RING;
+ (
+ FRAME_BUF.iter().map(|frame| frame.lock().clone()).collect(),
+ frame_index,
+ )
+}
@@ -556,14 +556,21 @@ pub(crate) trait PlatformWindow: HasWindowHandle + HasDisplayHandle {
}
}
+/// Bungus
+#[derive(Debug)]
+pub struct Bigus {
+ /// Bungys 2
+ pub location: &'static core::panic::Location<'static>,
+}
+
/// This type is public so that our test macro can generate and use it, but it should not
/// be considered part of our public API.
#[doc(hidden)]
pub trait PlatformDispatcher: Send + Sync {
fn is_main_thread(&self) -> bool;
- fn dispatch(&self, runnable: Runnable, label: Option<TaskLabel>);
- fn dispatch_on_main_thread(&self, runnable: Runnable);
- fn dispatch_after(&self, duration: Duration, runnable: Runnable);
+ fn dispatch(&self, runnable: Runnable<Bigus>, label: Option<TaskLabel>);
+ fn dispatch_on_main_thread(&self, runnable: Runnable<Bigus>);
+ fn dispatch_after(&self, duration: Duration, runnable: Runnable<Bigus>);
fn now(&self) -> Instant {
Instant::now()
}
@@ -17,11 +17,11 @@ use windows::{
};
use crate::{
- HWND, PlatformDispatcher, SafeHwnd, TaskLabel, WM_GPUI_TASK_DISPATCHED_ON_MAIN_THREAD,
+ Bigus, HWND, PlatformDispatcher, SafeHwnd, TaskLabel, WM_GPUI_TASK_DISPATCHED_ON_MAIN_THREAD,
};
pub(crate) struct WindowsDispatcher {
- main_sender: Sender<Runnable>,
+ main_sender: Sender<Runnable<Bigus>>,
main_thread_id: ThreadId,
platform_window_handle: SafeHwnd,
validation_number: usize,
@@ -29,7 +29,7 @@ pub(crate) struct WindowsDispatcher {
impl WindowsDispatcher {
pub(crate) fn new(
- main_sender: Sender<Runnable>,
+ main_sender: Sender<Runnable<Bigus>>,
platform_window_handle: HWND,
validation_number: usize,
) -> Self {
@@ -44,7 +44,7 @@ impl WindowsDispatcher {
}
}
- fn dispatch_on_threadpool(&self, runnable: Runnable) {
+ fn dispatch_on_threadpool(&self, runnable: Runnable<Bigus>) {
let handler = {
let mut task_wrapper = Some(runnable);
WorkItemHandler::new(move |_| {
@@ -55,7 +55,7 @@ impl WindowsDispatcher {
ThreadPool::RunWithPriorityAsync(&handler, WorkItemPriority::High).log_err();
}
- fn dispatch_on_threadpool_after(&self, runnable: Runnable, duration: Duration) {
+ fn dispatch_on_threadpool_after(&self, runnable: Runnable<Bigus>, duration: Duration) {
let handler = {
let mut task_wrapper = Some(runnable);
TimerElapsedHandler::new(move |_| {
@@ -72,14 +72,14 @@ impl PlatformDispatcher for WindowsDispatcher {
current().id() == self.main_thread_id
}
- fn dispatch(&self, runnable: Runnable, label: Option<TaskLabel>) {
+ fn dispatch(&self, runnable: Runnable<Bigus>, label: Option<TaskLabel>) {
self.dispatch_on_threadpool(runnable);
if let Some(label) = label {
log::debug!("TaskLabel: {label:?}");
}
}
- fn dispatch_on_main_thread(&self, runnable: Runnable) {
+ fn dispatch_on_main_thread(&self, runnable: Runnable<Bigus>) {
match self.main_sender.send(runnable) {
Ok(_) => unsafe {
PostMessageW(
@@ -104,7 +104,7 @@ impl PlatformDispatcher for WindowsDispatcher {
}
}
- fn dispatch_after(&self, duration: Duration, runnable: Runnable) {
+ fn dispatch_after(&self, duration: Duration, runnable: Runnable<Bigus>) {
self.dispatch_on_threadpool_after(runnable, duration);
}
}
@@ -1,4 +1,4 @@
-use std::rc::Rc;
+use std::{rc::Rc, time::Instant};
use ::util::ResultExt;
use anyhow::Context as _;
@@ -240,9 +240,7 @@ impl WindowsWindowInner {
fn handle_timer_msg(&self, handle: HWND, wparam: WPARAM) -> Option<isize> {
if wparam.0 == SIZE_MOVE_LOOP_TIMER_ID {
- for runnable in self.main_receiver.drain() {
- runnable.run();
- }
+ drain_main_receiver(&self.main_receiver);
self.handle_paint_msg(handle)
} else {
None
@@ -1194,13 +1192,28 @@ impl WindowsWindowInner {
#[inline]
fn draw_window(&self, handle: HWND, force_render: bool) -> Option<isize> {
+ let cur_frame = FRAME_INDEX
+ .fetch_add(1, std::sync::atomic::Ordering::SeqCst)
+ .overflowing_add(1)
+ .0
+ % FRAME_RING;
+
+ FRAME_BUF[cur_frame].lock().timings.clear();
+
let mut request_frame = self.state.borrow_mut().callbacks.request_frame.take()?;
+ let start = Instant::now();
request_frame(RequestFrameOptions {
require_presentation: false,
force_render,
});
+ let end = Instant::now();
+ let duration = end.duration_since(start).as_secs_f64();
+
self.state.borrow_mut().callbacks.request_frame = Some(request_frame);
unsafe { ValidateRect(Some(handle), None).ok().log_err() };
+
+ FRAME_BUF[cur_frame].lock().frame_time = duration;
+
Some(0)
}
@@ -4,15 +4,18 @@ use std::{
mem::ManuallyDrop,
path::{Path, PathBuf},
rc::{Rc, Weak},
- sync::Arc,
+ sync::{Arc, LazyLock, atomic::AtomicUsize},
+ time::Instant,
};
use ::util::{ResultExt, paths::SanitizedPath};
use anyhow::{Context as _, Result, anyhow};
use async_task::Runnable;
+use collections::HashMap;
use futures::channel::oneshot::{self, Receiver};
use itertools::Itertools;
-use parking_lot::RwLock;
+use log::info;
+use parking_lot::{Mutex, RwLock};
use smallvec::SmallVec;
use windows::{
UI::ViewManagement::UISettings,
@@ -47,7 +50,7 @@ struct WindowsPlatformInner {
raw_window_handles: std::sync::Weak<RwLock<SmallVec<[SafeHwnd; 4]>>>,
// The below members will never change throughout the entire lifecycle of the app.
validation_number: usize,
- main_receiver: flume::Receiver<Runnable>,
+ main_receiver: flume::Receiver<Runnable<Bigus>>,
}
pub(crate) struct WindowsPlatformState {
@@ -93,7 +96,7 @@ impl WindowsPlatform {
OleInitialize(None).context("unable to initialize Windows OLE")?;
}
let directx_devices = DirectXDevices::new().context("Creating DirectX devices")?;
- let (main_sender, main_receiver) = flume::unbounded::<Runnable>();
+ let (main_sender, main_receiver) = flume::unbounded::<Runnable<Bigus>>();
let validation_number = if usize::BITS == 64 {
rand::random::<u64>() as usize
} else {
@@ -746,9 +749,7 @@ impl WindowsPlatformInner {
#[inline]
fn run_foreground_task(&self) -> Option<isize> {
- for runnable in self.main_receiver.drain() {
- runnable.run();
- }
+ drain_main_receiver(&self.main_receiver);
Some(0)
}
@@ -796,6 +797,26 @@ impl WindowsPlatformInner {
}
}
+pub(crate) fn drain_main_receiver(main_receiver: &flume::Receiver<Runnable<Bigus>>) {
+ let mut timings = HashMap::default();
+
+ for runnable in main_receiver.drain() {
+ let name = runnable.metadata().location;
+ let start = Instant::now();
+ runnable.run();
+ let end = Instant::now();
+ *timings.entry(name).or_insert(0f64) += end.duration_since(start).as_secs_f64();
+ }
+
+ let frame_buf = {
+ let index = FRAME_INDEX.load(std::sync::atomic::Ordering::Acquire) % FRAME_RING;
+ let mut frame_buf = &*FRAME_BUF;
+ frame_buf[index].clone()
+ };
+ let mut frame_buf = frame_buf.lock();
+ frame_buf.timings.extend(timings);
+}
+
impl Drop for WindowsPlatform {
fn drop(&mut self) {
unsafe {
@@ -822,7 +843,7 @@ pub(crate) struct WindowCreationInfo {
pub(crate) windows_version: WindowsVersion,
pub(crate) drop_target_helper: IDropTargetHelper,
pub(crate) validation_number: usize,
- pub(crate) main_receiver: flume::Receiver<Runnable>,
+ pub(crate) main_receiver: flume::Receiver<Runnable<Bigus>>,
pub(crate) platform_window_handle: HWND,
pub(crate) disable_direct_composition: bool,
pub(crate) directx_devices: DirectXDevices,
@@ -832,7 +853,7 @@ struct PlatformWindowCreateContext {
inner: Option<Result<Rc<WindowsPlatformInner>>>,
raw_window_handles: std::sync::Weak<RwLock<SmallVec<[SafeHwnd; 4]>>>,
validation_number: usize,
- main_receiver: Option<flume::Receiver<Runnable>>,
+ main_receiver: Option<flume::Receiver<Runnable<Bigus>>>,
directx_devices: Option<DirectXDevices>,
}
@@ -72,7 +72,7 @@ pub(crate) struct WindowsWindowInner {
pub(crate) executor: ForegroundExecutor,
pub(crate) windows_version: WindowsVersion,
pub(crate) validation_number: usize,
- pub(crate) main_receiver: flume::Receiver<Runnable>,
+ pub(crate) main_receiver: flume::Receiver<Runnable<Bigus>>,
pub(crate) platform_window_handle: HWND,
}
@@ -351,7 +351,7 @@ struct WindowCreateContext {
windows_version: WindowsVersion,
drop_target_helper: IDropTargetHelper,
validation_number: usize,
- main_receiver: flume::Receiver<Runnable>,
+ main_receiver: flume::Receiver<Runnable<Bigus>>,
platform_window_handle: HWND,
appearance: WindowAppearance,
disable_direct_composition: bool,
@@ -30,7 +30,7 @@ pub use crate::session::Session;
pub const KERNEL_DOCS_URL: &str = "https://zed.dev/docs/repl#changing-kernels";
pub fn init(fs: Arc<dyn Fs>, cx: &mut App) {
- set_dispatcher(zed_dispatcher(cx));
+ // set_dispatcher(zed_dispatcher(cx));
JupyterSettings::register(cx);
::editor::init_settings(cx);
ReplSettings::register(cx);
@@ -38,26 +38,26 @@ pub fn init(fs: Arc<dyn Fs>, cx: &mut App) {
ReplStore::init(fs, cx);
}
-fn zed_dispatcher(cx: &mut App) -> impl Dispatcher {
- struct ZedDispatcher {
- dispatcher: Arc<dyn PlatformDispatcher>,
- }
-
- // PlatformDispatcher is _super_ close to the same interface we put in
- // async-dispatcher, except for the task label in dispatch. Later we should
- // just make that consistent so we have this dispatcher ready to go for
- // other crates in Zed.
- impl Dispatcher for ZedDispatcher {
- fn dispatch(&self, runnable: Runnable) {
- self.dispatcher.dispatch(runnable, None)
- }
-
- fn dispatch_after(&self, duration: Duration, runnable: Runnable) {
- self.dispatcher.dispatch_after(duration, runnable);
- }
- }
-
- ZedDispatcher {
- dispatcher: cx.background_executor().dispatcher.clone(),
- }
-}
+// fn zed_dispatcher(cx: &mut App) -> impl Dispatcher {
+// struct ZedDispatcher {
+// dispatcher: Arc<dyn PlatformDispatcher>,
+// }
+
+// // PlatformDispatcher is _super_ close to the same interface we put in
+// // async-dispatcher, except for the task label in dispatch. Later we should
+// // just make that consistent so we have this dispatcher ready to go for
+// // other crates in Zed.
+// impl Dispatcher for ZedDispatcher {
+// fn dispatch(&self, runnable: Runnable) {
+// self.dispatcher.dispatch(runnable, None)
+// }
+
+// fn dispatch_after(&self, duration: Duration, runnable: Runnable) {
+// self.dispatcher.dispatch_after(duration, runnable);
+// }
+// }
+
+// ZedDispatcher {
+// dispatcher: cx.background_executor().dispatcher.clone(),
+// }
+// }
@@ -29,10 +29,11 @@ use git_ui::commit_view::CommitViewToolbar;
use git_ui::git_panel::GitPanel;
use git_ui::project_diff::ProjectDiffToolbar;
use gpui::{
- Action, App, AppContext as _, Context, DismissEvent, Element, Entity, Focusable, KeyBinding,
- ParentElement, PathPromptOptions, PromptLevel, ReadGlobal, SharedString, Styled, Task,
- TitlebarOptions, UpdateGlobal, Window, WindowKind, WindowOptions, actions, image_cache, point,
- px, retain_all,
+ Action, App, AppContext as _, AsyncApp, Context, DismissEvent, Element, Entity, EventEmitter,
+ FRAME_RING, FocusHandle, Focusable, FrameTimings, Hsla, KeyBinding, MouseButton, ParentElement,
+ PathPromptOptions, PromptLevel, ReadGlobal, SharedString, Styled, Task, TitlebarOptions,
+ UpdateGlobal, Window, WindowKind, WindowOptions, actions, get_all_timings, get_frame_timings,
+ hsla, image_cache, point, px, retain_all, rgb, rgba,
};
use image_viewer::ImageInfo;
use language::Capability;
@@ -72,12 +73,13 @@ use std::{
};
use terminal_view::terminal_panel::{self, TerminalPanel};
use theme::{ActiveTheme, GlobalTheme, SystemAppearance, ThemeRegistry, ThemeSettings};
-use ui::{PopoverMenuHandle, prelude::*};
+use ui::{PopoverMenuHandle, Tooltip, prelude::*};
use util::markdown::MarkdownString;
use util::rel_path::RelPath;
use util::{ResultExt, asset_str};
use uuid::Uuid;
use vim_mode_setting::VimModeSetting;
+use workspace::dock::PanelEvent;
use workspace::notifications::{
NotificationId, SuppressEvent, dismiss_app_notification, show_app_notification,
};
@@ -87,7 +89,7 @@ use workspace::{
open_new,
};
use workspace::{
- CloseIntent, CloseWindow, NotificationFrame, RestoreBanner, with_active_or_new_workspace,
+ CloseIntent, CloseWindow, NotificationFrame, Panel, RestoreBanner, with_active_or_new_workspace,
};
use workspace::{Pane, notifications::DetachAndPromptErr};
use zed_actions::{
@@ -568,6 +570,327 @@ fn show_software_emulation_warning_if_needed(
}
}
+actions!(timings, [ToggleFocus,]);
+
+enum DataMode {
+ Realtime(Option<FrameTimings>),
+ Capture {
+ selected_index: usize,
+ data: Vec<FrameTimings>,
+ },
+}
+
+struct TimingsPanel {
+ position: workspace::dock::DockPosition,
+ focus_handle: FocusHandle,
+ data: DataMode,
+ width: Option<Pixels>,
+ _refresh: Option<Task<()>>,
+}
+
+impl TimingsPanel {
+ fn new(cx: &mut App) -> Entity<Self> {
+ let entity = cx.new(|cx| Self {
+ position: workspace::dock::DockPosition::Right,
+ focus_handle: cx.focus_handle(),
+ data: DataMode::Realtime(None),
+ width: None,
+ _refresh: Some(Self::begin_listen(cx)),
+ });
+
+ entity
+ }
+
+ fn begin_listen(cx: &mut Context<Self>) -> Task<()> {
+ cx.spawn(async move |this, cx| {
+ loop {
+ let data = get_frame_timings();
+
+ this.update(cx, |this: &mut TimingsPanel, cx| {
+ this.data = DataMode::Realtime(Some(data));
+ cx.notify();
+ });
+
+ cx.background_executor()
+ .timer(Duration::from_micros(1))
+ .await;
+ }
+ })
+ }
+
+ fn get_timings(&self) -> Option<&FrameTimings> {
+ match &self.data {
+ DataMode::Realtime(data) => data.as_ref(),
+ DataMode::Capture {
+ data,
+ selected_index,
+ } => Some(&data[*selected_index]),
+ }
+ }
+
+ fn render_bar(&self, max_value: f32, item: BarChartItem, cx: &App) -> impl IntoElement {
+ let fill_width = (item.value / max_value).max(0.02); // Minimum 2% width for visibility
+ let label = format!(
+ "{}:{}:{}",
+ item.location
+ .file()
+ .rsplit_once("/")
+ .unwrap_or(("", item.location.file()))
+ .1
+ .rsplit_once("\\")
+ .unwrap_or(("", item.location.file()))
+ .1,
+ item.location.line(),
+ item.location.column()
+ );
+
+ h_flex()
+ .gap_2()
+ .w_full()
+ .h(px(32.0)) // Slightly taller for better visibility
+ .child(
+ // Label with flexible width and truncation
+ div()
+ .min_w(px(80.0))
+ .max_w(px(200.0)) // Maximum width for labels
+ .flex_shrink_0()
+ .overflow_hidden()
+ .child(div().text_ellipsis().child(label)),
+ )
+ .child(
+ // Bar container
+ div()
+ .flex_1()
+ .h(px(24.0))
+ .bg(cx.theme().colors().background)
+ .rounded_md()
+ .p(px(2.0))
+ .child(
+ // Bar fill with minimum width
+ div()
+ .h_full()
+ .rounded_sm()
+ .bg(item.color)
+ .min_w(px(4.0)) // Minimum width so tiny values are still visible
+ .w(relative(fill_width)),
+ ),
+ )
+ .child(
+ // Value label - right-aligned
+ div()
+ .min_w(px(60.0))
+ .flex_shrink_0()
+ .text_right()
+ .child(format!("{:.1}", fill_width)),
+ )
+ }
+}
+
+#[derive(IntoElement)]
+struct DiscreteSlider {
+ value: usize,
+ count: usize,
+ on_change: Arc<dyn Fn(usize, &mut Window, &mut App)>,
+}
+
+impl DiscreteSlider {
+ pub fn new(
+ value: usize,
+ count: usize,
+ on_change: impl Fn(usize, &mut Window, &mut App) + 'static,
+ ) -> Self {
+ Self {
+ value: value.min(count - 1),
+ count,
+ on_change: Arc::new(on_change),
+ }
+ }
+}
+
+impl RenderOnce for DiscreteSlider {
+ fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
+ h_flex()
+ .id("discrete-slider")
+ .overflow_scroll()
+ .w_full()
+ .p_2()
+ .children((0..self.count).map(|i| {
+ let is_active = i == self.value;
+ let on_change = self.on_change.clone();
+
+ div()
+ .w(px(24.0))
+ .h(px(24.0))
+ .rounded_md()
+ .cursor_pointer()
+ .bg(if is_active {
+ cx.theme().accents().color_for_index(i as u32)
+ } else {
+ cx.theme().colors().element_background
+ })
+ .hover(|this| this.bg(cx.theme().colors().element_hover))
+ .on_mouse_down(MouseButton::Left, {
+ let on_change = Arc::clone(&on_change);
+ move |_, window, cx| {
+ on_change(i, window, cx);
+ }
+ })
+ // This handles drag selection
+ .on_mouse_move({
+ move |event, window, cx| {
+ // If mouse button is held down
+ if event.pressed_button == Some(MouseButton::Left) {
+ on_change(i, window, cx);
+ }
+ }
+ })
+ }))
+ }
+}
+
+struct BarChartItem {
+ location: &'static core::panic::Location<'static>,
+ value: f32,
+ color: Hsla,
+}
+
+impl Panel for TimingsPanel {
+ fn persistent_name() -> &'static str {
+ "Timings"
+ }
+
+ fn panel_key() -> &'static str {
+ "timings-panel"
+ }
+
+ fn position(&self, window: &Window, cx: &App) -> workspace::dock::DockPosition {
+ self.position
+ }
+
+ fn position_is_valid(&self, position: workspace::dock::DockPosition) -> bool {
+ true
+ }
+
+ fn set_position(
+ &mut self,
+ position: workspace::dock::DockPosition,
+ window: &mut Window,
+ cx: &mut Context<Self>,
+ ) {
+ self.position = position;
+ cx.notify();
+ }
+
+ fn size(&self, window: &Window, cx: &App) -> Pixels {
+ self.width.unwrap_or(px(200.0))
+ }
+
+ fn set_size(&mut self, size: Option<Pixels>, window: &mut Window, cx: &mut Context<Self>) {
+ self.width = size;
+ cx.notify();
+ }
+
+ fn icon(&self, window: &Window, cx: &App) -> Option<ui::IconName> {
+ Some(ui::IconName::Envelope)
+ }
+
+ fn icon_tooltip(&self, window: &Window, cx: &App) -> Option<&'static str> {
+ Some("Timings Panel")
+ }
+
+ fn toggle_action(&self) -> Box<dyn Action> {
+ Box::new(ToggleFocus)
+ }
+
+ fn activation_priority(&self) -> u32 {
+ 2
+ }
+}
+
+impl Render for TimingsPanel {
+ fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
+ v_flex()
+ .w_full()
+ .gap_2()
+ .child(
+ Button::new(
+ "switch-mode",
+ match self.data {
+ DataMode::Capture { .. } => "Realtime",
+ DataMode::Realtime(_) => "Capture",
+ },
+ )
+ .style(ButtonStyle::Filled)
+ .on_click(cx.listener(|this, _, window, cx| {
+ match this.data {
+ DataMode::Realtime(_) => {
+ let (data, selected_index) = get_all_timings();
+ this._refresh = None;
+ this.data = DataMode::Capture {
+ selected_index,
+ data,
+ };
+ }
+ DataMode::Capture { .. } => {
+ this._refresh = Some(Self::begin_listen(cx));
+ this.data = DataMode::Realtime(None);
+ }
+ };
+ cx.notify();
+ })),
+ )
+ .when(matches!(self.data, DataMode::Capture { .. }), |this| {
+ let DataMode::Capture {
+ selected_index,
+ data,
+ } = &self.data
+ else {
+ unreachable!();
+ };
+
+ let entity = cx.entity().downgrade();
+ this.child(DiscreteSlider::new(
+ *selected_index,
+ data.len(),
+ move |value, _window, cx| {
+ if let Some(entity) = entity.upgrade() {
+ entity.update(cx, |this, cx| {
+ if let DataMode::Capture { selected_index, .. } = &mut this.data {
+ *selected_index = value;
+ cx.notify();
+ }
+ });
+ }
+ },
+ ))
+ })
+ .when_some(self.get_timings(), |div, e| {
+ div.child(format!("{}", e.frame_time))
+ .children(e.timings.iter().enumerate().map(|(i, (name, value))| {
+ self.render_bar(
+ e.frame_time as f32,
+ BarChartItem {
+ location: name,
+ value: *value as f32,
+ color: cx.theme().accents().color_for_index(i as u32),
+ },
+ cx,
+ )
+ }))
+ })
+ }
+}
+
+impl Focusable for TimingsPanel {
+ fn focus_handle(&self, cx: &App) -> FocusHandle {
+ self.focus_handle.clone()
+ }
+}
+
+impl EventEmitter<workspace::Event> for TimingsPanel {}
+
+impl EventEmitter<PanelEvent> for TimingsPanel {}
+
fn initialize_panels(
prompt_builder: Arc<PromptBuilder>,
window: &mut Window,
@@ -585,6 +908,15 @@ fn initialize_panels(
cx.clone(),
);
let debug_panel = DebugPanel::load(workspace_handle.clone(), cx);
+ let timings_panel = workspace_handle
+ .update_in(cx, |workspace, window, cx| TimingsPanel::new(cx))
+ .unwrap();
+
+ workspace_handle.update_in(cx, |workspace, window, cx| {
+ workspace.register_action(|workspace, _: &ToggleFocus, window, cx| {
+ workspace.toggle_panel_focus::<TimingsPanel>(window, cx);
+ });
+ });
let (
project_panel,
@@ -612,6 +944,7 @@ fn initialize_panels(
workspace.add_panel(channels_panel, window, cx);
workspace.add_panel(notification_panel, window, cx);
workspace.add_panel(debug_panel, window, cx);
+ workspace.add_panel(timings_panel, window, cx);
})?;
fn setup_or_teardown_agent_panel(