From 4203854bae048474bc832864bc8a387e01b400b7 Mon Sep 17 00:00:00 2001 From: Elliott Slaughter Date: Tue, 9 Jul 2024 16:20:27 -0700 Subject: [PATCH] Display more timestamp resolution when interval is short (#55) --- src/app.rs | 56 +++-- src/timestamp.rs | 525 ++++++++++++++++++++++++++++++++++++++++------- 2 files changed, 482 insertions(+), 99 deletions(-) diff --git a/src/app.rs b/src/app.rs index 9d41726..8dbd7cc 100644 --- a/src/app.rs +++ b/src/app.rs @@ -19,7 +19,9 @@ use crate::data::{ ItemMeta, ItemUID, SlotMetaTileData, SlotTileData, SummaryTileData, TileID, TileSet, UtilPoint, }; use crate::deferred_data::{CountingDeferredDataSource, DeferredDataSource}; -use crate::timestamp::{Interval, Timestamp, TimestampParseError}; +use crate::timestamp::{ + Interval, Timestamp, TimestampDisplay, TimestampParseError, TimestampUnits, +}; /// Overview: /// ProfApp -> Context, Window * @@ -2186,10 +2188,31 @@ impl ProfApp { let time = (hover.x - rect.left()) / rect.width(); let time = cx.view_interval.lerp(time); + let label_text = if let Some(drag) = drag_interval { + format!("{drag}") + } else { + let units: TimestampUnits = cx.view_interval.into(); + let time_units = TimestampDisplay { + timestamp: time, + units, + include_units: true, + }; + format!("t={time_units}") + }; + + let label_size = { + let label_margin = ui.spacing().window_margin; + let available_width = ui.available_width() - 2.0 * label_margin.sum().x; + let label_text: egui::WidgetText = (&label_text).into(); + let label_text = + label_text.into_galley(ui, None, available_width, egui::TextStyle::Body); + label_text.size() + 2.0 * label_margin.sum() + }; + // Hack: This avoids an issue where popups displayed normally are // forced to stack, even when an explicit position is // requested. Instead we display the popup manually via black magic - let popup_size = if drag_interval.is_some() { 300.0 } else { 90.0 }; + let popup_size = label_size.x; let mut popup_rect = Rect::from_min_size( Pos2::new(top.x + HOVER_PADDING, top.y), Vec2::new(popup_size, 100.0), @@ -2208,14 +2231,8 @@ impl ProfApp { popup_rect.expand(16.0), ); egui::Frame::popup(ui.style()).show(&mut popup_ui, |ui| { - if let Some(drag) = drag_interval { - ui.label(format!("{drag}")); - } else { - ui.label(format!("t={time}")); - } + ui.label(label_text); }); - - // ui.show_tooltip_at("timestamp_tooltip", Some(top), format!("t={time}")); } } @@ -2709,12 +2726,6 @@ trait UiExtra { rect: &Rect, add_contents: impl FnOnce(&mut egui::Ui), ); - fn show_tooltip_at( - &mut self, - id_source: impl core::hash::Hash, - suggested_position: Option, - text: impl Into, - ); } impl UiExtra for egui::Ui { @@ -2752,21 +2763,6 @@ impl UiExtra for egui::Ui { add_contents, ); } - fn show_tooltip_at( - &mut self, - id_source: impl core::hash::Hash, - suggested_position: Option, - text: impl Into, - ) { - egui::containers::show_tooltip_at( - self.ctx(), - self.auto_id_with(id_source), - suggested_position, - |ui| { - ui.add(egui::Label::new(text)); - }, - ); - } } #[cfg(not(target_arch = "wasm32"))] diff --git a/src/timestamp.rs b/src/timestamp.rs index cec2d12..0362a7d 100644 --- a/src/timestamp.rs +++ b/src/timestamp.rs @@ -39,32 +39,16 @@ impl Timestamp { impl fmt::Display for Timestamp { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - // Time is stored in nanoseconds. But display in larger units if possible. - let ns = self.0; - let ns_per_us = 1_000; - let ns_per_ms = 1_000_000; - let ns_per_s = 1_000_000_000; - let divisor; - let remainder_divisor; - let mut unit_name = "ns"; - if ns >= ns_per_s { - divisor = ns_per_s; - remainder_divisor = divisor / 1_000; - unit_name = "s"; - } else if ns >= ns_per_ms { - divisor = ns_per_ms; - remainder_divisor = divisor / 1_000; - unit_name = "ms"; - } else if ns >= ns_per_us { - divisor = ns_per_us; - remainder_divisor = divisor / 1_000; - unit_name = "us"; - } else { - return write!(f, "{ns} {unit_name}"); - } - let units = ns / divisor; - let remainder = (ns % divisor) / remainder_divisor; - write!(f, "{units}.{remainder:0>3} {unit_name}") + let units: TimestampUnits = (*self).into(); + write!( + f, + "{}", + TimestampDisplay { + timestamp: *self, + units, + include_units: true + } + ) } } @@ -76,50 +60,27 @@ pub struct Interval { impl fmt::Display for Interval { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - // Time is stored in nanoseconds. But display in larger units if possible. - let start_ns = self.start.0; - let stop_ns = self.stop.0; - let ns_per_us = 1_000; - let ns_per_ms = 1_000_000; - let ns_per_s = 1_000_000_000; - let divisor; - let remainder_divisor; - let mut unit_name = "ns"; - if stop_ns >= ns_per_s { - divisor = ns_per_s; - remainder_divisor = divisor / 1_000; - unit_name = "s"; - } else if stop_ns >= ns_per_ms { - divisor = ns_per_ms; - remainder_divisor = divisor / 1_000; - unit_name = "ms"; - } else if stop_ns >= ns_per_us { - divisor = ns_per_us; - remainder_divisor = divisor / 1_000; - unit_name = "us"; - } else { - return write!( - f, - "from {} to {} {} (duration: {})", - start_ns, - stop_ns, - unit_name, - Timestamp(self.duration_ns()) - ); - } - let start_units = start_ns / divisor; - let start_remainder = (start_ns % divisor) / remainder_divisor; - let stop_units = stop_ns / divisor; - let stop_remainder = (stop_ns % divisor) / remainder_divisor; + let units: TimestampUnits = (*self).into(); + let duration = Timestamp(self.duration_ns()); + let duration_units: TimestampUnits = duration.into(); write!( f, - "from {}.{:0>3} to {}.{:0>3} {} (duration: {})", - start_units, - start_remainder, - stop_units, - stop_remainder, - unit_name, - Timestamp(self.duration_ns()) + "from {} to {} (duration: {})", + TimestampDisplay { + timestamp: self.start, + units, + include_units: false + }, + TimestampDisplay { + timestamp: self.stop, + units, + include_units: true + }, + TimestampDisplay { + timestamp: duration, + units: duration_units, + include_units: true + } ) } } @@ -176,11 +137,141 @@ impl Interval { } } +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub struct TimestampUnits { + divisor: i64, + digits_after_separator: i64, + unit_name: &'static str, +} + +impl From for TimestampUnits { + fn from(timestamp: Timestamp) -> TimestampUnits { + // Time is stored in nanoseconds. But display in larger units if possible. + let ns = timestamp.0; + const NS_PER_NS: i64 = 1; + const NS_PER_US: i64 = 1_000; + const NS_PER_MS: i64 = 1_000_000; + const NS_PER_S: i64 = 1_000_000_000; + let divisor; + let digits_after_separator; + let unit_name; + if ns >= NS_PER_S { + divisor = NS_PER_S; + digits_after_separator = 3; + unit_name = "s"; + } else if ns >= NS_PER_MS { + divisor = NS_PER_MS; + digits_after_separator = 3; + unit_name = "ms"; + } else if ns >= NS_PER_US { + divisor = NS_PER_US; + digits_after_separator = 3; + unit_name = "us"; + } else { + divisor = NS_PER_NS; + digits_after_separator = 0; + unit_name = "ns"; + } + TimestampUnits { + divisor, + digits_after_separator, + unit_name, + } + } +} + +impl From for TimestampUnits { + fn from(interval: Interval) -> TimestampUnits { + // Time is stored in nanoseconds. But display in larger units if possible. + let ns = interval.stop.0; + let duration = interval.duration_ns(); + const NS_PER_NS: i64 = 1; + const NS_PER_US: i64 = 1_000; + const NS_PER_MS: i64 = 1_000_000; + const NS_PER_S: i64 = 1_000_000_000; + let divisor; + let digits_after_separator; + let unit_name; + if ns >= NS_PER_S { + divisor = NS_PER_S; + if duration >= NS_PER_MS { + digits_after_separator = 3; + } else if duration >= NS_PER_US { + digits_after_separator = 6; + } else { + digits_after_separator = 9; + } + unit_name = "s"; + } else if ns >= NS_PER_MS { + divisor = NS_PER_MS; + if duration >= NS_PER_US { + digits_after_separator = 3; + } else { + digits_after_separator = 6; + } + unit_name = "ms"; + } else if ns >= NS_PER_US { + divisor = NS_PER_US; + digits_after_separator = 3; + unit_name = "us"; + } else { + divisor = NS_PER_NS; + digits_after_separator = 0; + unit_name = "ns"; + } + TimestampUnits { + divisor, + digits_after_separator, + unit_name, + } + } +} + +#[derive(Debug, Copy, Clone)] +pub struct TimestampDisplay { + pub timestamp: Timestamp, + pub units: TimestampUnits, + pub include_units: bool, +} + +impl fmt::Display for TimestampDisplay { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let TimestampUnits { + divisor, + unit_name, + digits_after_separator, + } = self.units; + let ns = self.timestamp.0; + let units = ns / divisor; + write!(f, "{units}")?; + if digits_after_separator > 0 { + write!(f, ".")?; + let remainder = ns % divisor; + if digits_after_separator >= 3 { + let r0 = remainder / (divisor / 1_000); + write!(f, "{r0:0>3}")?; + } + if digits_after_separator >= 6 { + let r1 = remainder / (divisor / 1_000_000) % 1_000; + write!(f, " {r1:0>3}")?; + } + if digits_after_separator >= 9 { + let r2 = remainder / (divisor / 1_000_000_000) % 1_000; + write!(f, " {r2:0>3}")?; + } + } + if self.include_units { + write!(f, " {unit_name}")?; + } + Ok(()) + } +} + #[cfg(test)] mod tests { use super::*; - mod timestamp { + mod timestamp_parse { use super::*; #[test] @@ -415,4 +506,300 @@ mod tests { assert_eq!(origin.translate(-250), expect); } } + + mod timestamp_units_from_timestamp { + use super::*; + + #[test] + fn test_s() { + let units: TimestampUnits = Timestamp(123_456_789_012).into(); + assert_eq!( + units, + TimestampUnits { + divisor: 1_000_000_000, + unit_name: "s", + digits_after_separator: 3 + } + ); + } + + #[test] + fn test_ms() { + let units: TimestampUnits = Timestamp(123_456_789).into(); + assert_eq!( + units, + TimestampUnits { + divisor: 1_000_000, + unit_name: "ms", + digits_after_separator: 3 + } + ); + } + + #[test] + fn test_us() { + let units: TimestampUnits = Timestamp(123_456).into(); + assert_eq!( + units, + TimestampUnits { + divisor: 1_000, + unit_name: "us", + digits_after_separator: 3 + } + ); + } + + #[test] + fn test_ns() { + let units: TimestampUnits = Timestamp(123).into(); + assert_eq!( + units, + TimestampUnits { + divisor: 1, + unit_name: "ns", + digits_after_separator: 0 + } + ); + } + } + + mod timestamp_units_from_interval { + use super::*; + + #[test] + fn test_s() { + let i0 = Interval::new(Timestamp(0), Timestamp(123_456_789_012)); + let units: TimestampUnits = i0.into(); + assert_eq!( + units, + TimestampUnits { + divisor: 1_000_000_000, + unit_name: "s", + digits_after_separator: 3 + } + ); + } + + #[test] + fn test_s_duration_ms() { + let i0 = Interval::new(Timestamp(123_000_000_000), Timestamp(123_456_789_012)); + let units: TimestampUnits = i0.into(); + assert_eq!( + units, + TimestampUnits { + divisor: 1_000_000_000, + unit_name: "s", + digits_after_separator: 3 + } + ); + } + + #[test] + fn test_s_duration_us() { + let i0 = Interval::new(Timestamp(123_456_000_000), Timestamp(123_456_789_012)); + let units: TimestampUnits = i0.into(); + assert_eq!( + units, + TimestampUnits { + divisor: 1_000_000_000, + unit_name: "s", + digits_after_separator: 6 + } + ); + } + + #[test] + fn test_s_duration_ns() { + let i0 = Interval::new(Timestamp(123_456_789_000), Timestamp(123_456_789_012)); + let units: TimestampUnits = i0.into(); + assert_eq!( + units, + TimestampUnits { + divisor: 1_000_000_000, + unit_name: "s", + digits_after_separator: 9 + } + ); + } + + #[test] + fn test_ms() { + let i0 = Interval::new(Timestamp(0), Timestamp(123_456_789)); + let units: TimestampUnits = i0.into(); + assert_eq!( + units, + TimestampUnits { + divisor: 1_000_000, + unit_name: "ms", + digits_after_separator: 3 + } + ); + } + + #[test] + fn test_ms_duration_us() { + let i0 = Interval::new(Timestamp(123_000_000), Timestamp(123_456_789)); + let units: TimestampUnits = i0.into(); + assert_eq!( + units, + TimestampUnits { + divisor: 1_000_000, + unit_name: "ms", + digits_after_separator: 3 + } + ); + } + + #[test] + fn test_ms_duration_ns() { + let i0 = Interval::new(Timestamp(123_456_000), Timestamp(123_456_789)); + let units: TimestampUnits = i0.into(); + assert_eq!( + units, + TimestampUnits { + divisor: 1_000_000, + unit_name: "ms", + digits_after_separator: 6 + } + ); + } + + #[test] + fn test_us() { + let i0 = Interval::new(Timestamp(0), Timestamp(123_456)); + let units: TimestampUnits = i0.into(); + assert_eq!( + units, + TimestampUnits { + divisor: 1_000, + unit_name: "us", + digits_after_separator: 3 + } + ); + } + + #[test] + fn test_ns() { + let i0 = Interval::new(Timestamp(123), Timestamp(456)); + let units: TimestampUnits = i0.into(); + assert_eq!( + units, + TimestampUnits { + divisor: 1, + unit_name: "ns", + digits_after_separator: 0 + } + ); + } + } + + mod timestamp_display { + use super::*; + + #[test] + fn test_s() { + let t0 = Timestamp(123_456_789_012); + assert_eq!(&format!("{}", t0), "123.456 s"); + } + + #[test] + fn test_ms() { + let t0 = Timestamp(123_456_789); + assert_eq!(&format!("{}", t0), "123.456 ms"); + } + + #[test] + fn test_us() { + let t0 = Timestamp(123_456); + assert_eq!(&format!("{}", t0), "123.456 us"); + } + + #[test] + fn test_ns() { + let t0 = Timestamp(123); + assert_eq!(&format!("{}", t0), "123 ns"); + } + } + + mod interval_display { + use super::*; + + #[test] + fn test_s() { + let i0 = Interval::new(Timestamp(0), Timestamp(123_456_789_012)); + assert_eq!( + &format!("{}", i0), + "from 0.000 to 123.456 s (duration: 123.456 s)" + ); + } + + #[test] + fn test_s_duration_ms() { + let i0 = Interval::new(Timestamp(123_000_000_000), Timestamp(123_456_789_012)); + assert_eq!( + &format!("{}", i0), + "from 123.000 to 123.456 s (duration: 456.789 ms)" + ) + } + + #[test] + fn test_s_duration_us() { + let i0 = Interval::new(Timestamp(123_456_000_000), Timestamp(123_456_789_012)); + assert_eq!( + &format!("{}", i0), + "from 123.456 000 to 123.456 789 s (duration: 789.012 us)" + ); + } + + #[test] + fn test_s_duration_ns() { + let i0 = Interval::new(Timestamp(123_456_789_000), Timestamp(123_456_789_012)); + assert_eq!( + &format!("{}", i0), + "from 123.456 789 000 to 123.456 789 012 s (duration: 12 ns)" + ); + } + + #[test] + fn test_ms() { + let i0 = Interval::new(Timestamp(0), Timestamp(123_456_789)); + assert_eq!( + &format!("{}", i0), + "from 0.000 to 123.456 ms (duration: 123.456 ms)" + ); + } + + #[test] + fn test_ms_duration_us() { + let i0 = Interval::new(Timestamp(123_000_000), Timestamp(123_456_789)); + assert_eq!( + &format!("{}", i0), + "from 123.000 to 123.456 ms (duration: 456.789 us)" + ); + } + + #[test] + fn test_ms_duration_ns() { + let i0 = Interval::new(Timestamp(123_456_000), Timestamp(123_456_789)); + assert_eq!( + &format!("{}", i0), + "from 123.456 000 to 123.456 789 ms (duration: 789 ns)" + ); + } + + #[test] + fn test_us() { + let i0 = Interval::new(Timestamp(0), Timestamp(123_456)); + assert_eq!( + &format!("{}", i0), + "from 0.000 to 123.456 us (duration: 123.456 us)" + ); + } + + #[test] + fn test_ns() { + let i0 = Interval::new(Timestamp(0), Timestamp(123)); + assert_eq!(&format!("{}", i0), "from 0 to 123 ns (duration: 123 ns)"); + } + } }