Compare commits

..

2 Commits

3 changed files with 154 additions and 237 deletions

View File

@@ -1,41 +1,43 @@
use num_width::NumberWidth;
use std::time::Duration;
use smallvec::SmallVec;
use std::{iter, time::Duration};
// Helper to split a duration into a integer, decimal, and unit
fn get_value(duration: &Duration) -> (u64, u32, &'static str) {
let (int, decimal, unit) = match duration {
// if greater than 1 second, return as seconds
n if n >= &Duration::from_secs(1) => {
let secs = n.as_secs();
let decimal = n.as_millis() as u64 % 1000;
(secs, decimal as u32, "s")
}
// if greater than 1 millisecond, return as milliseconds
n if n >= &Duration::from_millis(1) => {
let ms = n.as_millis() as u64;
let decimal = n.as_micros() as u64 % 1000;
(ms, decimal as u32, "ms")
}
// if greater than 1 microsecond, return as microseconds
n if n >= &Duration::from_micros(1) => {
let us = n.as_micros() as u64;
let decimal = n.as_nanos() as u64 % 1000;
(us, decimal as u32, "µs")
}
// otherwise, return as nanoseconds
n => {
let ns = n.as_nanos() as u64;
(ns, 0, "ns")
}
};
(int, decimal, unit)
}
/// Formats timing data into a vector of strings with proper alignment
pub fn format_timing_display(timing_data: Vec<(String, Duration, Duration)>) -> String {
if timing_data.is_empty() {
return String::new();
}
// Helper to split a duration into a integer, decimal, and unit
fn get_value(duration: &Duration) -> (u64, u32, &'static str) {
let (int, decimal, unit) = match duration {
// if greater than 1 second, return as seconds
n if n >= &Duration::from_secs(1) => {
let secs = n.as_secs();
let decimal = n.as_millis() as u64 % 1000;
(secs, decimal as u32, "s")
}
// if greater than 1 millisecond, return as milliseconds
n if n >= &Duration::from_millis(1) => {
let ms = n.as_millis() as u64;
let decimal = n.as_micros() as u64 % 1000;
(ms, decimal as u32, "ms")
}
// if greater than 1 microsecond, return as microseconds
n if n >= &Duration::from_micros(1) => {
let us = n.as_micros() as u64;
let decimal = n.as_nanos() as u64 % 1000;
(us, decimal as u32, "µs")
}
// otherwise, return as nanoseconds
n => {
let ns = n.as_nanos() as u64;
(ns, 0, "ns")
}
};
(int, decimal, unit)
pub fn format_timing_display(timing_data: impl IntoIterator<Item = (String, Duration, Duration)>) -> SmallVec<[String; 12]> {
let mut iter = timing_data.into_iter().peekable();
if iter.peek().is_none() {
return SmallVec::new();
}
struct Entry {
@@ -48,8 +50,7 @@ pub fn format_timing_display(timing_data: Vec<(String, Duration, Duration)>) ->
std_unit: &'static str,
}
let entries = timing_data
.iter()
let mut entries = iter
.map(|(name, avg, std_dev)| {
let (avg_int, avg_decimal, avg_unit) = get_value(&avg);
let (std_int, std_decimal, std_unit) = get_value(&std_dev);
@@ -64,84 +65,38 @@ pub fn format_timing_display(timing_data: Vec<(String, Duration, Duration)>) ->
std_unit,
}
})
.collect::<Vec<_>>();
.collect::<SmallVec<[Entry; 12]>>();
let max_name_width = entries.iter().map(|e| e.name.len() as usize).max().unwrap_or(0);
let max_avg_int_width = entries.iter().map(|e| e.avg_int.width() as usize).max().unwrap_or(0);
let max_avg_decimal_width = entries
let (max_name_width, max_avg_int_width, max_avg_decimal_width, max_std_int_width, max_std_decimal_width) = entries
.iter()
.map(|e| e.avg_decimal.width() as usize)
.max()
.unwrap_or(0)
.max(3);
let max_std_int_width = entries.iter().map(|e| e.std_int.width() as usize).max().unwrap_or(0);
let max_std_decimal_width = entries
.iter()
.map(|e| e.std_decimal.width() as usize)
.max()
.unwrap_or(0)
.max(3);
.fold((0, 0, 3, 0, 3), |(name_w, avg_int_w, avg_dec_w, std_int_w, std_dec_w), e| {
(
name_w.max(e.name.len()),
avg_int_w.max(e.avg_int.width() as usize),
avg_dec_w.max(e.avg_decimal.width() as usize),
std_int_w.max(e.std_int.width() as usize),
std_dec_w.max(e.std_decimal.width() as usize),
)
});
let mut output_lines = Vec::new();
// Format each line using the calculated max widths for alignment
for Entry {
name,
avg_int,
avg_decimal,
avg_unit,
std_int,
std_decimal,
std_unit,
} in entries.iter()
{
output_lines.push(format!(
"{name:max_name_width$} : {avg_int:max_avg_int_width$}.{avg_decimal:<max_avg_decimal_width$}{avg_unit} ± {std_int:max_std_int_width$}.{std_decimal:<max_std_decimal_width$}{std_unit}"
));
}
output_lines.join("\n")
}
#[cfg(test)]
mod tests {
use super::*;
use pretty_assertions::assert_eq;
use std::time::Duration;
#[test]
fn test_format_timing_display() {
let timing_data = vec![
("total".to_string(), Duration::from_micros(1234), Duration::from_micros(570)),
("input".to_string(), Duration::from_micros(120), Duration::from_micros(45)),
("player".to_string(), Duration::from_micros(456), Duration::from_micros(123)),
("movement".to_string(), Duration::from_micros(789), Duration::from_micros(234)),
("render".to_string(), Duration::from_micros(12), Duration::from_micros(3)),
("debug".to_string(), Duration::from_nanos(460), Duration::from_nanos(557)),
];
let result = format_timing_display(timing_data);
let lines: Vec<&str> = result.lines().collect();
// Verify we have the expected number of lines
assert_eq!(lines.len(), 6);
let expected = r#"
total : 1.234ms ± 570.0 µs
input : 120.0 µs ± 45.0 µs
player : 456.0 µs ± 123.0 µs
movement : 789.0 µs ± 234.0 µs
render : 12.0 µs ± 3.0 µs
debug : 460.0 ns ± 557.0 ns
"#
.trim();
for (line, expected_line) in lines.iter().zip(expected.lines()) {
assert_eq!(*line, expected_line);
}
// Print the result for manual inspection
println!("Formatted output:");
println!("{}", result);
}
entries.iter().map(|e| {
format!(
"{name:max_name_width$} : {avg_int:max_avg_int_width$}.{avg_decimal:<max_avg_decimal_width$}{avg_unit} ± {std_int:max_std_int_width$}.{std_decimal:<max_std_decimal_width$}{std_unit}",
// Content
name = e.name,
avg_int = e.avg_int,
avg_decimal = e.avg_decimal,
std_int = e.std_int,
std_decimal = e.std_decimal,
// Units
avg_unit = e.avg_unit,
std_unit = e.std_unit,
// Padding
max_name_width = max_name_width,
max_avg_int_width = max_avg_int_width,
max_avg_decimal_width = max_avg_decimal_width,
max_std_int_width = max_std_int_width,
max_std_decimal_width = max_std_decimal_width
)
}).collect::<SmallVec<[String; 12]>>()
}

View File

@@ -126,7 +126,7 @@ impl SystemTimings {
}
// Use the formatting module to format the data
crate::systems::formatting::format_timing_display(timing_data)
crate::systems::formatting::format_timing_display(timing_data).join("\n")
}
}

View File

@@ -1,135 +1,97 @@
use itertools::izip;
use pacman::systems::formatting::format_timing_display;
use smallvec::SmallVec;
use std::time::Duration;
#[test]
fn test_basic_formatting() {
let timing_data = vec![
("60 FPS".to_string(), Duration::from_micros(1234), Duration::from_micros(567)),
("input".to_string(), Duration::from_micros(123), Duration::from_micros(45)),
use pretty_assertions::assert_eq;
fn get_timing_data() -> Vec<(String, Duration, Duration)> {
vec![
("total".to_string(), Duration::from_micros(1234), Duration::from_micros(570)),
("input".to_string(), Duration::from_micros(120), Duration::from_micros(45)),
("player".to_string(), Duration::from_micros(456), Duration::from_micros(123)),
("movement".to_string(), Duration::from_micros(789), Duration::from_micros(234)),
("render".to_string(), Duration::from_micros(12), Duration::from_micros(3)),
("debug".to_string(), Duration::from_nanos(1000000), Duration::from_nanos(1000)),
];
("debug".to_string(), Duration::from_nanos(460), Duration::from_nanos(557)),
]
}
let result = format_timing_display(timing_data);
println!("Basic formatting test:");
println!("{}", result);
println!();
fn get_formatted_output() -> impl IntoIterator<Item = String> {
format_timing_display(get_timing_data())
}
#[test]
fn test_desired_format() {
// This test represents the exact format you want to achieve
let timing_data = vec![
("total".to_string(), Duration::from_micros(1230), Duration::from_micros(570)),
("input".to_string(), Duration::from_micros(120), Duration::from_micros(50)),
("player".to_string(), Duration::from_micros(460), Duration::from_micros(120)),
("movement".to_string(), Duration::from_micros(790), Duration::from_micros(230)),
("render".to_string(), Duration::from_micros(10), Duration::from_micros(3)),
("debug".to_string(), Duration::from_nanos(1000000), Duration::from_nanos(1000)),
];
fn test_formatting_alignment() {
let mut colon_positions = vec![];
let mut first_decimal_positions = vec![];
let mut second_decimal_positions = vec![];
let mut first_unit_positions = vec![];
let mut second_unit_positions = vec![];
let result = format_timing_display(timing_data);
println!("Desired format test:");
println!("{}", result);
println!();
get_formatted_output().into_iter().for_each(|line| {
let (mut got_decimal, mut got_unit) = (false, false);
for (i, char) in line.chars().enumerate() {
match char {
':' => colon_positions.push(i),
'.' => {
if got_decimal {
second_decimal_positions.push(i);
} else {
first_decimal_positions.push(i);
}
got_decimal = true;
}
's' => {
if got_unit {
first_unit_positions.push(i);
} else {
second_unit_positions.push(i);
got_unit = true;
}
}
_ => {}
}
}
});
// Expected output should look like:
// total : 1.23 ms ± 0.57 ms
// input : 0.12 ms ± 0.05 ms
// player : 0.46 ms ± 0.12 ms
// movement : 0.79 ms ± 0.23 ms
// render : 0.01 ms ± 0.003ms
// debug : 0.001ms ± 0.000ms
}
#[test]
fn test_mixed_units() {
let timing_data = vec![
("60 FPS".to_string(), Duration::from_millis(16), Duration::from_micros(500)),
(
"fast_system".to_string(),
Duration::from_nanos(500000),
Duration::from_nanos(100000),
),
(
"medium_system".to_string(),
Duration::from_micros(2500),
Duration::from_micros(500),
),
("slow_system".to_string(), Duration::from_millis(5), Duration::from_millis(1)),
];
let result = format_timing_display(timing_data);
println!("Mixed units test:");
println!("{}", result);
println!();
}
#[test]
fn test_trailing_zeros() {
let timing_data = vec![
("60 FPS".to_string(), Duration::from_micros(1000), Duration::from_micros(500)),
("exact_ms".to_string(), Duration::from_millis(1), Duration::from_micros(100)),
("exact_us".to_string(), Duration::from_micros(1), Duration::from_nanos(100000)),
("exact_ns".to_string(), Duration::from_nanos(1000), Duration::from_nanos(100)),
];
let result = format_timing_display(timing_data);
println!("Trailing zeros test:");
println!("{}", result);
println!();
}
#[test]
fn test_edge_cases() {
let timing_data = vec![
("60 FPS".to_string(), Duration::from_nanos(1), Duration::from_nanos(1)),
("very_small".to_string(), Duration::from_nanos(100), Duration::from_nanos(50)),
("very_large".to_string(), Duration::from_secs(1), Duration::from_millis(100)),
("zero_time".to_string(), Duration::ZERO, Duration::ZERO),
];
let result = format_timing_display(timing_data);
println!("Edge cases test:");
println!("{}", result);
println!();
}
#[test]
fn test_variable_name_lengths() {
let timing_data = vec![
("60 FPS".to_string(), Duration::from_micros(1234), Duration::from_micros(567)),
("a".to_string(), Duration::from_micros(123), Duration::from_micros(45)),
(
"very_long_system_name".to_string(),
Duration::from_micros(456),
Duration::from_micros(123),
),
("medium".to_string(), Duration::from_micros(789), Duration::from_micros(234)),
];
let result = format_timing_display(timing_data);
println!("Variable name lengths test:");
println!("{}", result);
println!();
}
#[test]
fn test_empty_input() {
let timing_data = vec![];
let result = format_timing_display(timing_data);
assert_eq!(result, "");
println!("Empty input test: PASS");
}
#[test]
fn test_single_entry() {
let timing_data = vec![("60 FPS".to_string(), Duration::from_micros(1234), Duration::from_micros(567))];
let result = format_timing_display(timing_data);
println!("Single entry test:");
println!("{}", result);
println!();
// Assert that all positions were found
assert_eq!(
vec![
&colon_positions,
&first_decimal_positions,
&second_decimal_positions,
&first_unit_positions,
&second_unit_positions
]
.iter()
.all(|p| p.len() == 6),
true
);
// Assert that all positions are the same
assert!(
colon_positions.iter().all(|&p| p == colon_positions[0]),
"colon positions are not the same {:?}",
colon_positions
);
assert!(
first_decimal_positions.iter().all(|&p| p == first_decimal_positions[0]),
"first decimal positions are not the same {:?}",
first_decimal_positions
);
assert!(
second_decimal_positions.iter().all(|&p| p == second_decimal_positions[0]),
"second decimal positions are not the same {:?}",
second_decimal_positions
);
assert!(
first_unit_positions.iter().all(|&p| p == first_unit_positions[0]),
"first unit positions are not the same {:?}",
first_unit_positions
);
assert!(
second_unit_positions.iter().all(|&p| p == second_unit_positions[0]),
"second unit positions are not the same {:?}",
second_unit_positions
);
}