From f1935ad0169199a3970ec0e1ed62faee5aa96ab1 Mon Sep 17 00:00:00 2001 From: Xevion Date: Fri, 15 Aug 2025 19:06:43 -0500 Subject: [PATCH] refactor: use smallvec instead of collect string, explicit formatting, accumulator fold --- src/systems/formatting.rs | 183 ++++++++++++++------------------------ src/systems/profiling.rs | 2 +- 2 files changed, 70 insertions(+), 115 deletions(-) diff --git a/src/systems/formatting.rs b/src/systems/formatting.rs index d101f90..9f417d2 100644 --- a/src/systems/formatting.rs +++ b/src/systems/formatting.rs @@ -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) -> 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::>(); + .collect::>(); - 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: = 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:>() } diff --git a/src/systems/profiling.rs b/src/systems/profiling.rs index b635aea..5ddac4d 100644 --- a/src/systems/profiling.rs +++ b/src/systems/profiling.rs @@ -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") } }